Merged changes from 2.3 branch

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@71172 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Andrew O'Neil 2009-02-01 23:49:53 +00:00
parent 2eb73817c2
commit 60f75c5ca4
130 changed files with 1246 additions and 833 deletions

View File

@ -25,7 +25,6 @@ Director::addRules(10, array(
'$Controller//$Action/$ID/$OtherID' => '*', '$Controller//$Action/$ID/$OtherID' => '*',
'images' => 'Image_Uploader', 'images' => 'Image_Uploader',
'' => 'RootURLController', '' => 'RootURLController',
'sitemap.xml' => 'GoogleSitemap',
'api/v1' => 'RestfulServer', 'api/v1' => 'RestfulServer',
'soap/v1' => 'SOAPModelAccess', 'soap/v1' => 'SOAPModelAccess',
'dev' => 'DevelopmentAdmin', 'dev' => 'DevelopmentAdmin',
@ -47,7 +46,7 @@ Object::useCustomClass('Datetime','SSDatetime',true);
* Add pear parser to include path * Add pear parser to include path
*/ */
$path = Director::baseFolder().'/sapphire/parsers/'; $path = Director::baseFolder().'/sapphire/parsers/';
set_include_path(get_include_path() . PATH_SEPARATOR . $path); set_include_path(str_replace('.' . PATH_SEPARATOR, '.' . PATH_SEPARATOR . $path . PATH_SEPARATOR, get_include_path()));
/** /**
* Define a default language different than english * Define a default language different than english
@ -70,7 +69,4 @@ define('MCE_ROOT', 'jsparty/tiny_mce2/');
*/ */
define('EMAIL_BOUNCEHANDLER_KEY', '1aaaf8fb60ea253dbf6efa71baaacbb3'); define('EMAIL_BOUNCEHANDLER_KEY', '1aaaf8fb60ea253dbf6efa71baaacbb3');
?> ?>

View File

@ -193,6 +193,7 @@ class RSSFeed extends ViewableData {
* Return the content of the RSS feed * Return the content of the RSS feed
*/ */
function feedContent() { function feedContent() {
SSViewer::set_source_file_comments(false);
return str_replace(' ', ' ', $this->renderWith('RSSFeed')); return str_replace(' ', ' ', $this->renderWith('RSSFeed'));
} }
} }

View File

@ -137,6 +137,11 @@ class RestfulServer extends Controller {
$id = (isset($this->urlParams['ID'])) ? $this->urlParams['ID'] : null; $id = (isset($this->urlParams['ID'])) ? $this->urlParams['ID'] : null;
$relation = (isset($this->urlParams['Relation'])) ? $this->urlParams['Relation'] : null; $relation = (isset($this->urlParams['Relation'])) ? $this->urlParams['Relation'] : null;
// Check input formats
if(!class_exists($className)) return $this->notFound();
if($id && !is_numeric($id)) return $this->notFound();
if($relation && !preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $relation)) return $this->notFound();
// if api access is disabled, don't proceed // if api access is disabled, don't proceed
$apiAccess = singleton($className)->stat('api_access'); $apiAccess = singleton($className)->stat('api_access');
if(!$apiAccess) return $this->permissionFailure(); if(!$apiAccess) return $this->permissionFailure();

View File

@ -125,7 +125,14 @@ class RestfulService extends ViewableData {
$responseBody = curl_exec($ch); $responseBody = curl_exec($ch);
$curlError = curl_error($ch); $curlError = curl_error($ch);
if($curlError) { // Problem verifying the server SSL certificate; just ignore it as it's not mandatory
if(strpos($curlError,'14090086') !== false) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$responseBody = curl_exec($ch);
$curlError = curl_error($ch);
}
if($responseBody === false) {
user_error("Curl Error:" . $curlError, E_USER_WARNING); user_error("Curl Error:" . $curlError, E_USER_WARNING);
return; return;
} }

View File

@ -29,7 +29,7 @@ class XMLDataFormatter extends DataFormatter {
* @return String XML * @return String XML
*/ */
public function convertDataObject(DataObjectInterface $obj, $fields = null) { public function convertDataObject(DataObjectInterface $obj, $fields = null) {
Controller::curr()->getResponse()->addHeader("Content-type", "text/xml"); Controller::curr()->getResponse()->addHeader("Content-Type", "text/xml");
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . $this->convertDataObjectWithoutHeader($obj, $fields); return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . $this->convertDataObjectWithoutHeader($obj, $fields);
} }
@ -111,7 +111,7 @@ class XMLDataFormatter extends DataFormatter {
* @return String XML * @return String XML
*/ */
public function convertDataObjectSet(DataObjectSet $set, $fields = null) { public function convertDataObjectSet(DataObjectSet $set, $fields = null) {
Controller::curr()->getResponse()->addHeader("Content-type", "text/xml"); Controller::curr()->getResponse()->addHeader("Content-Type", "text/xml");
$className = $set->class; $className = $set->class;
$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";

View File

@ -11,8 +11,10 @@
*/ */
abstract class CliController extends Controller { abstract class CliController extends Controller {
function init() { function init() {
$this->disableBasicAuth(); $this->disableBasicAuth();
parent::init(); parent::init();
// Unless called from the command line, all CliControllers need ADMIN privileges
if(!Director::is_cli() && !Permission::check("ADMIN")) return Security::permissionFailure();
} }
function index() { function index() {
@ -30,4 +32,5 @@ abstract class CliController extends Controller {
*/ */
function process() {} function process() {}
} }
?> ?>

View File

@ -93,16 +93,36 @@ class ClassInfo {
/** /**
* Returns a list of classes that inherit from the given class. * Returns a list of classes that inherit from the given class.
* The resulting array includes the base class passed
* through the $class parameter as the first array value.
*
* Example usage:
* <example>
* ClassInfo::subclassesFor('BaseClass');
* array(
* 0 => 'BaseClass',
* 'ChildClass' => 'ChildClass',
* 'GrandChildClass' => 'GrandChildClass'
* )
* </example>
* *
* @param mixed $class string of the classname or instance of the class * @param mixed $class string of the classname or instance of the class
* @return array * @return array Names of all subclasses as an associative array.
*/ */
static function subclassesFor($class){ static function subclassesFor($class){
global $_ALL_CLASSES; global $_ALL_CLASSES;
if (is_object($class)) $class = get_class($class); if (is_object($class)) $class = get_class($class);
// get all classes from the manifest
$subclasses = isset($_ALL_CLASSES['children'][$class]) ? $_ALL_CLASSES['children'][$class] : null; $subclasses = isset($_ALL_CLASSES['children'][$class]) ? $_ALL_CLASSES['children'][$class] : null;
if(isset($subclasses)) array_unshift($subclasses, $class);
else $subclasses[$class] = $class; // add the base class to the array
if(isset($subclasses)) {
array_unshift($subclasses, $class);
} else {
$subclasses[$class] = $class;
}
return $subclasses; return $subclasses;
} }

View File

@ -286,5 +286,4 @@ function _t($entity, $string = "", $priority = 40, $context = "") {
return i18n::_t($entity, $string, $priority, $context); return i18n::_t($entity, $string, $priority, $context);
} }
?> ?>

View File

@ -102,11 +102,9 @@ class HTTP {
static function findByTagAndAttribute($content, $attribs) { static function findByTagAndAttribute($content, $attribs) {
$regExps = array(); $regExps = array();
$content = '';
foreach($attribs as $tag => $attrib) { foreach($attribs as $tag => $attrib) {
if(!is_numeric($tag)) $tagPrefix = "$tag "; $tagPrefix = (is_numeric($tag)) ? '' : "$tag ";
else $tagPrefix = "";
$regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *\")([^\"]*)(\")/ie"; $regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *\")([^\"]*)(\")/ie";
$regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *')([^']*)(')/ie"; $regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *')([^']*)(')/ie";
@ -125,6 +123,7 @@ class HTTP {
static function getLinksIn($content) { static function getLinksIn($content) {
return self::findByTagAndAttribute($content, array("a" => "href")); return self::findByTagAndAttribute($content, array("a" => "href"));
} }
static function getImagesIn($content) { static function getImagesIn($content) {
return self::findByTagAndAttribute($content, array("img" => "src")); return self::findByTagAndAttribute($content, array("img" => "src"));
} }
@ -362,5 +361,4 @@ class HTTP {
} }
?> ?>

View File

@ -549,5 +549,4 @@ class ManifestBuilder {
} }
} }
?> ?>

View File

@ -516,10 +516,6 @@ class Object {
Object::$extraStatics[$className]['extensions'] = array_diff(Object::$extraStatics[$className]['extensions'], array($extensionName)); Object::$extraStatics[$className]['extensions'] = array_diff(Object::$extraStatics[$className]['extensions'], array($extensionName));
} }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CACHE METHODS (added by simon_w (simon -at- simon -dot- geek -dot- nz))
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/** /**
* Loads a current cache from the filesystem, if it can. * Loads a current cache from the filesystem, if it can.
* *
@ -606,8 +602,6 @@ class Object {
* @param string|int $id An id for the cache * @param string|int $id An id for the cache
* @return mixed The cached return of the method * @return mixed The cached return of the method
*/ */
// I know this is almost exactly the same as cacheToFile, but call_user_func_array() is slow.
// Which is why there's two separate functions
public function cacheToFileWithArgs($callback, $args = array(), $expire = 3600, $id = false) { public function cacheToFileWithArgs($callback, $args = array(), $expire = 3600, $id = false) {
if(!$this->class) { if(!$this->class) {
$this->class = get_class($this); $this->class = get_class($this);

View File

@ -54,16 +54,17 @@ class Requirements {
} }
/** /**
* Add the CSS styling to the header of the page * Include custom CSS styling to the header of the page.
* *
* See {@link Requirements_Backend::customCSS()} * See {@link Requirements_Backend::customCSS()}
*
* @param string $script CSS selectors as a string (without <style> tag enclosing selectors).
* @param int $uniquenessID Group CSS by a unique ID as to avoid duplicate custom CSS in header
*/ */
static function customCSS($script, $uniquenessID = null) { static function customCSS($script, $uniquenessID = null) {
self::backend()->custom($script, $uniquenessID); self::backend()->customCSS($script, $uniquenessID);
} }
/** /**
* Add the following custom code to the <head> section of the page. * Add the following custom code to the <head> section of the page.
* See {@link Requirements_Backend::insertHeadTags()} * See {@link Requirements_Backend::insertHeadTags()}
@ -245,7 +246,7 @@ class Requirements {
* Set whether you want to write the JS to the body of the page or * Set whether you want to write the JS to the body of the page or
* in the head section * in the head section
* *
* @see {@link Requirements_Backend::set_write_js_to_body()} * @see Requirements_Backend::set_write_js_to_body()
* @param boolean * @param boolean
*/ */
static function set_write_js_to_body($var) { static function set_write_js_to_body($var) {
@ -390,7 +391,12 @@ class Requirements_Backend {
$script .= "\n"; $script .= "\n";
} }
/**
* Include custom CSS styling to the header of the page.
*
* @param string $script CSS selectors as a string (without <style> tag enclosing selectors).
* @param int $uniquenessID Group CSS by a unique ID as to avoid duplicate custom CSS in header
*/
function customCSS($script, $uniquenessID = null) { function customCSS($script, $uniquenessID = null) {
if($uniquenessID) if($uniquenessID)
$this->customCSS[$uniquenessID] = $script; $this->customCSS[$uniquenessID] = $script;
@ -399,7 +405,6 @@ class Requirements_Backend {
} }
} }
/** /**
* Add the following custom code to the <head> section of the page. * Add the following custom code to the <head> section of the page.
* *
@ -444,7 +449,7 @@ class Requirements_Backend {
} }
function get_css() { function get_css() {
return array_diff_key($this->css,$this->blocked); return array_diff_key($this->css, $this->blocked);
} }
/** /**
@ -561,7 +566,7 @@ class Requirements_Backend {
$requirements .= "<link rel=\"stylesheet\" type=\"text/css\"{$media} href=\"$path\" />\n"; $requirements .= "<link rel=\"stylesheet\" type=\"text/css\"{$media} href=\"$path\" />\n";
} }
} }
foreach(array_diff_key($this->customCSS,$this->blocked) as $css) { foreach(array_diff_key($this->customCSS, $this->blocked) as $css) {
$requirements .= "<style type=\"text/css\">\n$css\n</style>\n"; $requirements .= "<style type=\"text/css\">\n$css\n</style>\n";
} }
@ -775,6 +780,7 @@ class Requirements_Backend {
* *
*/ */
function process_combined_files() { function process_combined_files() {
if(Director::isDev() && !SapphireTest::is_running_test()) { if(Director::isDev() && !SapphireTest::is_running_test()) {
return; return;
} }
@ -861,16 +867,7 @@ class Requirements_Backend {
// Unsuccessful write - just include the regular JS files, rather than the combined one // Unsuccessful write - just include the regular JS files, rather than the combined one
if(!$successfulWrite) { if(!$successfulWrite) {
user_error("Requirements_Backend::process_combined_files(): Couldn't create '$base$combinedFile'", E_USER_WARNING); user_error("Requirements_Backend::process_combined_files(): Couldn't create '$base$combinedFile'", E_USER_WARNING);
$keyedFileList = array(); return;
foreach($fileList as $file) $keyedFileList[$file] = true;
$combinedPos = array_search($combinedFile, array_keys($newJSRequirements));
if($combinedPos) {
$newJSRequirements = array_merge(
array_slice($newJSRequirements, 0, $combinedPos),
$keyedFileList,
array_slice($newJSRequirements, $combinedPos+1)
);
}
} }
} }
@ -925,5 +922,4 @@ class Requirements_Backend {
} }
?> ?>

View File

@ -48,16 +48,29 @@
* @subpackage view * @subpackage view
*/ */
class SSViewer extends Object { class SSViewer extends Object {
/**
* @var boolean $source_file_comments
*/
protected static $source_file_comments = true; protected static $source_file_comments = true;
/** /**
* Set whether HTML comments indicating the source .SS file used to render this page should be * Set whether HTML comments indicating the source .SS file used to render this page should be
* included in the output. This is enabled by default * included in the output. This is enabled by default
*
* @param boolean $val
*/ */
function set_source_file_comments($val) { static function set_source_file_comments($val) {
self::$source_file_comments = $val; self::$source_file_comments = $val;
} }
/**
* @return boolean
*/
static function get_source_file_comments() {
return self::$source_file_comments;
}
/** /**
* @var array $chosenTemplates Associative array for the different * @var array $chosenTemplates Associative array for the different
* template containers: "main" and "Layout". * template containers: "main" and "Layout".
@ -355,6 +368,7 @@ class SSViewer extends Object {
static function parseTemplateContent($content, $template="") { static function parseTemplateContent($content, $template="") {
// Add template filename comments on dev sites // Add template filename comments on dev sites
if(Director::isDev() && self::$source_file_comments && $template) { if(Director::isDev() && self::$source_file_comments && $template) {
// If this template is a full HTML page, then put the comments just inside the HTML tag to prevent any IE glitches // If this template is a full HTML page, then put the comments just inside the HTML tag to prevent any IE glitches
if(stripos($content, "<html") !== false) { if(stripos($content, "<html") !== false) {
@ -369,9 +383,9 @@ class SSViewer extends Object {
$oldContent = $content; $oldContent = $content;
// Add include filename comments on dev sites // Add include filename comments on dev sites
if(Director::isDev()) $replacementCode = 'return "<!-- include " . SSViewer::getTemplateFile($matches[1]) . "-->\n" if(Director::isDev() && self::$source_file_comments) $replacementCode = 'return "<!-- include " . SSViewer::getTemplateFile($matches[1]) . " -->\n"
. SSViewer::getTemplateContent($matches[1]) . SSViewer::getTemplateContent($matches[1])
. "\n<!-- end include " . SSViewer::getTemplateFile($matches[1]) . "-->";'; . "\n<!-- end include " . SSViewer::getTemplateFile($matches[1]) . " -->";';
else $replacementCode = 'return SSViewer::getTemplateContent($matches[1]);'; else $replacementCode = 'return SSViewer::getTemplateContent($matches[1]);';
$content = preg_replace_callback('/<' . '% include +([A-Za-z0-9_]+) +%' . '>/', create_function( $content = preg_replace_callback('/<' . '% include +([A-Za-z0-9_]+) +%' . '>/', create_function(

View File

@ -224,7 +224,7 @@ JS
if($this->dataRecord){ if($this->dataRecord){
$thisPage = $this->dataRecord->Link(); $thisPage = $this->dataRecord->Link();
$cmsLink = 'admin/show/' . $this->dataRecord->ID; $cmsLink = 'admin/show/' . $this->dataRecord->ID;
$cmsLink = "<a href=\"$cmsLink\" target=\"cms\">CMS</a>"; $cmsLink = "<a href=\"$cmsLink\" target=\"cms\">". _t('ContentController.CMS', 'CMS') ."</a>";
} else { } else {
/** /**
* HGS: If this variable is missing a notice is raised. Subclasses of ContentController * HGS: If this variable is missing a notice is raised. Subclasses of ContentController
@ -241,30 +241,30 @@ JS
$dateObj = Object::create('Datetime', $date, null); $dateObj = Object::create('Datetime', $date, null);
// $dateObj->setVal($date); // $dateObj->setVal($date);
$archiveLink = "<a class=\"current\">Archived Site</a>"; $archiveLink = "<a class=\"current\">". _t('ContentController.ARCHIVEDSITE', 'Archived Site') ."</a>";
$liveLink = "<a href=\"$thisPage?stage=Live\" target=\"site\" style=\"left : -3px;\">Published Site</a>"; $liveLink = "<a href=\"$thisPage?stage=Live\" target=\"site\" style=\"left : -3px;\">". _t('ContentController.PUBLISHEDSITE', 'Published Site') ."</a>";
$stageLink = "<a href=\"$thisPage?stage=Stage\" target=\"site\" style=\"left : -1px;\">Draft Site</a>"; $stageLink = "<a href=\"$thisPage?stage=Stage\" target=\"site\" style=\"left : -1px;\">". _t('ContentController.DRAFTSITE', 'Draft Site') ."</a>";
$message = "<div id=\"SilverStripeNavigatorMessage\" title=\"Note: this message won't be shown to your visitors\">Archived site from<br>" . $dateObj->Nice() . "</div>"; $message = "<div id=\"SilverStripeNavigatorMessage\" title='". _t('ContentControl.NOTEWONTBESHOWN', 'Note: this message won\'t be shown to your visitors') ."'>". _t('ContentController.ARCHIVEDSITEFROM', 'Archived site from') ."<br>" . $dateObj->Nice() . "</div>";
} else if(Versioned::current_stage() == 'Stage') { } else if(Versioned::current_stage() == 'Stage') {
$stageLink = "<a class=\"current\">Draft Site</a>"; $stageLink = "<a class=\"current\">". _t('ContentController.DRAFTSITE', 'Draft Site') ."</a>";
$liveLink = "<a href=\"$thisPage?stage=Live\" target=\"site\" style=\"left : -3px;\">Published Site</a>"; $liveLink = "<a href=\"$thisPage?stage=Live\" target=\"site\" style=\"left : -3px;\">". _t('ContentController.PUBLISHEDSITE', 'Published Site') ."</a>";
$message = "<div id=\"SilverStripeNavigatorMessage\" title=\"Note: this message won't be shown to your visitors\">DRAFT SITE</div>"; $message = "<div id=\"SilverStripeNavigatorMessage\" title='". _t('ContentControl.NOTEWONTBESHOWN', 'Note: this message won\'t be shown to your visitors') ."'>". _t('ContentController.DRAFTSITE', 'Draft Site') ."</div>";
} else { } else {
$liveLink = "<a class=\"current\">Published Site</a>"; $liveLink = "<a class=\"current\">". _t('ContentController.PUBLISHEDSITE', 'Published Site') ."</a>";
$stageLink = "<a href=\"$thisPage?stage=Stage\" target=\"site\" style=\"left : -1px;\">Draft Site</a>"; $stageLink = "<a href=\"$thisPage?stage=Stage\" target=\"site\" style=\"left : -1px;\">". _t('ContentController.DRAFTSITE', 'Draft Site') ."</a>";
$message = "<div id=\"SilverStripeNavigatorMessage\" title=\"Note: this message won't be shown to your visitors\">PUBLISHED SITE</div>"; $message = "<div id=\"SilverStripeNavigatorMessage\" title='". _t('ContentControl.NOTEWONTBESHOWN', 'Note: this message won\'t be shown to your visitors') ."'>". _t('ContentController.PUBLISHEDSITE', 'Published Site') ."</div>";
} }
if($member) { if($member) {
$firstname = Convert::raw2xml($member->FirstName); $firstname = Convert::raw2xml($member->FirstName);
$surname = Convert::raw2xml($member->Surame); $surname = Convert::raw2xml($member->Surame);
$logInMessage = "Logged in as {$firstname} {$surname} - <a href=\"Security/logout\">log out</a>"; $logInMessage = _t('ContentController.LOGGEDINAS', 'Logged in as') ." {$firstname} {$surname} - <a href=\"Security/logout\">". _t('ContentController.LOGOUT', 'Log out'). "</a>";
} else { } else {
$logInMessage = "Not logged in - <a href=\"Security/login\">log in</a>"; $logInMessage = _t('ContentController.NOTLOGGEDIN', 'Not logged in') ." - <a href='Security/login'>". _t('ContentController.LOGIN', 'Login') ."</a>";
} }
$viewPageIn = _t('ContentController.VIEWPAGEIN', 'View Page in:');
/** /**
* HGS: cmsLink is now only set if there is a dataRecord. You can't view the page in the * HGS: cmsLink is now only set if there is a dataRecord. You can't view the page in the
* CMS if there is no dataRecord * CMS if there is no dataRecord
@ -277,7 +277,7 @@ JS
</div> </div>
<div id="switchView" class="bottomTabs"> <div id="switchView" class="bottomTabs">
<div class="blank"> View page in: </div> <div class="blank">$viewPageIn </div>
$cmsLink $cmsLink
$stageLink $stageLink
<div class="blank" style="width:1em;"> </div> <div class="blank" style="width:1em;"> </div>
@ -295,7 +295,7 @@ HTML;
Requirements::css(SAPPHIRE_DIR . '/css/SilverStripeNavigator.css'); Requirements::css(SAPPHIRE_DIR . '/css/SilverStripeNavigator.css');
$dateObj = Object::create('Datetime', $date, null); $dateObj = Object::create('Datetime', $date, null);
// $dateObj->setVal($date); // $dateObj->setVal($date);
return "<div id=\"SilverStripeNavigatorMessage\">Archived site from<br>" . $dateObj->Nice() . "</div>"; return "<div id=\"SilverStripeNavigatorMessage\">". _t('ContentController.ARCHIVEDSITEFROM') ."<br>" . $dateObj->Nice() . "</div>";
} }
} }
} }
@ -423,5 +423,4 @@ HTML
} }
} }
?> ?>

View File

@ -112,7 +112,7 @@ class ContentNegotiator {
// Only serve "pure" XHTML if the XML header is present // Only serve "pure" XHTML if the XML header is present
if(substr($content,0,5) == '<' . '?xml' ) { if(substr($content,0,5) == '<' . '?xml' ) {
$response->addHeader("Content-type", "application/xhtml+xml; charset=" . self::$encoding); $response->addHeader("Content-Type", "application/xhtml+xml; charset=" . self::$encoding);
$response->addHeader("Vary" , "Accept"); $response->addHeader("Vary" , "Accept");
$content = str_replace('&nbsp;','&#160;', $content); $content = str_replace('&nbsp;','&#160;', $content);
@ -134,7 +134,7 @@ class ContentNegotiator {
* Removes "xmlns" attributes and any <?xml> Pragmas. * Removes "xmlns" attributes and any <?xml> Pragmas.
*/ */
function html(HTTPResponse $response) { function html(HTTPResponse $response) {
$response->addHeader("Content-type", "text/html; charset=" . self::$encoding); $response->addHeader("Content-Type", "text/html; charset=" . self::$encoding);
$response->addHeader("Vary", "Accept"); $response->addHeader("Vary", "Accept");
$content = $response->getBody(); $content = $response->getBody();
@ -174,6 +174,11 @@ class ContentNegotiator {
* By default, negotiation is only enabled for pages that have the xml header. * By default, negotiation is only enabled for pages that have the xml header.
*/ */
static function enabled_for($response) { static function enabled_for($response) {
$contentType = $response->getHeader("Content-Type");
// Disable content negotation for other content types
if($contentType && substr($contentType, 0,9) != 'text/html' && substr($contentType, 0,21) != 'application/xhtml+xml') return false;
if(self::$enabled) return true; if(self::$enabled) return true;
else return (substr($response->getBody(),0,5) == '<' . '?xml'); else return (substr($response->getBody(),0,5) == '<' . '?xml');
} }

View File

@ -217,7 +217,7 @@ class HTTPRequest extends Object implements ArrayAccess {
* @return string * @return string
*/ */
function getURL() { function getURL() {
return $this->url; return ($this->getExtension()) ? $this->url . '.' . $this->getExtension() : $this->url;
} }
/** /**

View File

@ -22,7 +22,7 @@ class RootURLController extends Controller {
$controller = new ModelAsController(); $controller = new ModelAsController();
$request = new HTTPRequest("GET", self::get_homepage_urlsegment().'/', $request->getVars(), $request->postVars()); $request = new HTTPRequest("GET", self::get_homepage_urlsegment().'/', $request->getVars(), $request->postVars());
$request->match('$URLSegment//$Action'); $request->match('$URLSegment//$Action', true);
$result = $controller->handleRequest($request); $result = $controller->handleRequest($request);

View File

@ -965,15 +965,26 @@ class i18n extends Object {
* Given a file name (a php class name, without the .php ext, or a template name, including the .ss extension) * Given a file name (a php class name, without the .php ext, or a template name, including the .ss extension)
* this helper function determines the module where this file is located * this helper function determines the module where this file is located
* *
* @param string $name php class name or template file name * @param string $name php class name or template file name (including *.ss extension)
* @return string Module where the file is located * @return string Module where the file is located
*/ */
public static function get_owner_module($name) { public static function get_owner_module($name) {
if (substr($name,-3) == '.ss') { // if $name is a template file
if(substr($name,-3) == '.ss') {
global $_TEMPLATE_MANIFEST; global $_TEMPLATE_MANIFEST;
$path = str_replace('\\','/',Director::makeRelative(current($_TEMPLATE_MANIFEST[substr($name,0,-3)]))); $templateManifest = $_TEMPLATE_MANIFEST[substr($name,0,-3)];
if(is_array($templateManifest) && isset($templateManifest['themes'])) {
$absolutePath = $templateManifest['themes'][SSViewer::current_theme()];
} else {
$absolutePath = $templateManifest;
}
$path = str_replace('\\','/',Director::makeRelative(current($absolutePath)));
ereg('/([^/]+)/',$path,$module); ereg('/([^/]+)/',$path,$module);
} else { }
// $name is assumed to be a PHP class
else {
global $_CLASS_MANIFEST; global $_CLASS_MANIFEST;
if(strpos($name,'_') !== false) $name = strtok($name,'_'); if(strpos($name,'_') !== false) $name = strtok($name,'_');
if(isset($_CLASS_MANIFEST[$name])) { if(isset($_CLASS_MANIFEST[$name])) {
@ -1076,6 +1087,7 @@ class i18n extends Object {
$module = self::get_owner_module($class); $module = self::get_owner_module($class);
if(!$module) user_error("i18n::include_by_class: Class {$class} not found", E_USER_WARNING); if(!$module) user_error("i18n::include_by_class: Class {$class} not found", E_USER_WARNING);
$locale = self::get_locale();
if (file_exists($file = Director::getAbsFile("$module/lang/". self::get_locale() . '.php'))) { if (file_exists($file = Director::getAbsFile("$module/lang/". self::get_locale() . '.php'))) {
include($file); include($file);
@ -1088,6 +1100,12 @@ class i18n extends Object {
} else if(file_exists(Director::getAbsFile("$module/lang"))) { } else if(file_exists(Director::getAbsFile("$module/lang"))) {
user_error("i18n::include_by_class: Locale file $file should exist", E_USER_WARNING); user_error("i18n::include_by_class: Locale file $file should exist", E_USER_WARNING);
} }
// If the language file wasn't included for this class, include an empty array to prevent
// this method from being called again
global $lang;
if(!isset($lang[$locale][$class])) $lang[$locale][$class] = array();
} }
//-----------------------------------------------------------------------------------------------// //-----------------------------------------------------------------------------------------------//

View File

@ -44,6 +44,23 @@ interface i18nEntityProvider {
* *
* Example usage in {@link DataObject->provideI18nEntities()}. * Example usage in {@link DataObject->provideI18nEntities()}.
* *
* You can ask textcollector to add the provided entity to a different module
* than the class is contained in by adding a 4th argument to the array:
* <code>
* class MyTestClass implements i18nEntityProvider {
* function provideI18nEntities() {
* $entities = array();
* $entities["MyOtherModuleClass.MYENTITY"] = array(
* $value,
* PR_MEDIUM,
* 'My context description',
* 'myothermodule'
* );
* }
* return $entities;
* }
* </code>
*
* @return array All entites in an associative array, with * @return array All entites in an associative array, with
* entity name as the key, and a numerical array of pseudo-arguments * entity name as the key, and a numerical array of pseudo-arguments
* for _t() as a value. * for _t() as a value.

View File

@ -110,6 +110,16 @@ class i18nTextCollector extends Object {
unset($entitiesByModule[$module][$fullName]); unset($entitiesByModule[$module][$fullName]);
} }
} }
// extract all entities for "foreign" modules (fourth argument)
foreach($entitiesByModule[$module] as $fullName => $spec) {
if(isset($spec[3]) && $spec[3] != $module) {
$othermodule = $spec[3];
if(!isset($entitiesByModule[$othermodule])) $entitiesByModule[$othermodule] = array();
unset($spec[3]);
$entitiesByModule[$othermodule][$fullName] = $spec;
}
}
} }
// Write the generated master string tables // Write the generated master string tables

View File

@ -1581,7 +1581,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
* Used for simple frontend forms without relation editing * Used for simple frontend forms without relation editing
* or {@link TabSet} behaviour. Uses {@link scaffoldFormFields()} * or {@link TabSet} behaviour. Uses {@link scaffoldFormFields()}
* by default. To customize, either overload this method in your * by default. To customize, either overload this method in your
* subclass, or decorate it by {@link DataObjectDecorator->updateFormFields()}. * subclass, or decorate it by {@link DataObjectDecorator->updateFrontEndFields()}.
* *
* @todo Decide on naming for "website|frontend|site|page" and stick with it in the API * @todo Decide on naming for "website|frontend|site|page" and stick with it in the API
* *
@ -1590,7 +1590,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
*/ */
public function getFrontEndFields($params = null) { public function getFrontEndFields($params = null) {
$untabbedFields = $this->scaffoldFormFields($params); $untabbedFields = $this->scaffoldFormFields($params);
$this->extend('updateFormFields', $untabbedFields); $this->extend('updateFrontEndFields', $untabbedFields);
return $untabbedFields; return $untabbedFields;
} }
@ -1836,7 +1836,10 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
* Uses the rules for whether the table should exist rather than actually looking in the database. * Uses the rules for whether the table should exist rather than actually looking in the database.
*/ */
public function has_own_table($dataClass) { public function has_own_table($dataClass) {
if(!is_subclass_of($dataClass,'DataObject')) return false; // The condition below has the same effect as !is_subclass_of($dataClass,'DataObject'),
// which causes PHP < 5.3 to segfault in rare circumstances, see PHP bug #46753
if($dataClass == 'DataObject' || !in_array('DataObject', ClassInfo::ancestry($dataClass))) return false;
if(!isset(self::$cache_has_own_table[$dataClass])) { if(!isset(self::$cache_has_own_table[$dataClass])) {
if(get_parent_class($dataClass) == 'DataObject') { if(get_parent_class($dataClass) == 'DataObject') {
self::$cache_has_own_table[$dataClass] = true; self::$cache_has_own_table[$dataClass] = true;
@ -2736,7 +2739,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
$fields = array(); $fields = array();
// try to scaffold a couple of usual suspects // try to scaffold a couple of usual suspects
if ($this->hasField('Name')) $fields['Name'] = 'Name'; if ($this->hasField('Name')) $fields['Name'] = 'Name';
if ($this->hasField('Title')) $fields['Title'] = 'Title'; if ($this->hasDataBaseField('Title')) $fields['Title'] = 'Title';
if ($this->hasField('Description')) $fields['Description'] = 'Description'; if ($this->hasField('Description')) $fields['Description'] = 'Description';
if ($this->hasField('FirstName')) $fields['FirstName'] = 'First Name'; if ($this->hasField('FirstName')) $fields['FirstName'] = 'First Name';
} }
@ -2848,7 +2851,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
/** /**
* Inserts standard column-values when a DataObject * Inserts standard column-values when a DataObject
* is instanciated. Does not insert default records {@see $default_records}. * is instanciated. Does not insert default records {@see $default_records}.
* This is a map from classname to default value. * This is a map from fieldname to default value.
* *
* - If you would like to change a default value in a sub-class, just specify it. * - If you would like to change a default value in a sub-class, just specify it.
* - If you would like to disable the default value given by a parent class, set the default value to 0,'',or false in your * - If you would like to disable the default value given by a parent class, set the default value to 0,'',or false in your

View File

@ -57,7 +57,6 @@ abstract class DataObjectDecorator extends Extension {
return $this->loadExtraStatics(); return $this->loadExtraStatics();
} }
/** /**
* Edit the given query object to support queries for this extension * Edit the given query object to support queries for this extension
* *
@ -66,7 +65,6 @@ abstract class DataObjectDecorator extends Extension {
function augmentSQL(SQLQuery &$query) { function augmentSQL(SQLQuery &$query) {
} }
/** /**
* Update the database schema as required by this extension. * Update the database schema as required by this extension.
*/ */
@ -139,7 +137,8 @@ abstract class DataObjectDecorator extends Extension {
/** /**
* This function is used to provide modifications to the form in the CMS * This function is used to provide modifications to the form in the CMS
* by the decorator. By default, no changes are made. * by the decorator. By default, no changes are made. {@link DataObject->getCMSFields()}.
*
* Please consider using {@link updateFormFields()} to globally add * Please consider using {@link updateFormFields()} to globally add
* formfields to the record. The method {@link updateCMSFields()} * formfields to the record. The method {@link updateCMSFields()}
* should just be used to add or modify tabs, or fields which * should just be used to add or modify tabs, or fields which
@ -153,16 +152,22 @@ abstract class DataObjectDecorator extends Extension {
} }
/** /**
* This function is used to provide modifications to the form in the CMS * This function is used to provide modifications to the form used
* by the decorator. * for front end forms. {@link DataObject->getFrontEndFields()}
* *
* Caution: Use {@link FieldSet->push()} to add fields. * Caution: Use {@link FieldSet->push()} to add fields.
* *
* @param FieldSet $fields FieldSet without TabSet nesting * @param FieldSet $fields FieldSet without TabSet nesting
*/ */
function updateFormFields(FieldSet &$fields) { function updateFrontEndFields(FieldSet &$fields) {
} }
/**
* This is used to provide modifications to the form actions
* used in the CMS. {@link DataObject->getCMSActions()}.
*
* @param FieldSet $actions FieldSet
*/
function updateCMSActions(FieldSet &$actions) { function updateCMSActions(FieldSet &$actions) {
} }
@ -176,6 +181,12 @@ abstract class DataObjectDecorator extends Extension {
$extra_fields = $this->extraStatics(); $extra_fields = $this->extraStatics();
if(isset($extra_fields['summary_fields'])){ if(isset($extra_fields['summary_fields'])){
$summary_fields = $extra_fields['summary_fields']; $summary_fields = $extra_fields['summary_fields'];
// if summary_fields were passed in numeric array,
// convert to an associative array
if($summary_fields && array_key_exists(0, $summary_fields)) {
$summary_fields = array_combine(array_values($summary_fields), array_values($summary_fields));
}
if($summary_fields) $fields = array_merge($fields, $summary_fields); if($summary_fields) $fields = array_merge($fields, $summary_fields);
} }
} }
@ -201,5 +212,4 @@ abstract class DataObjectDecorator extends Extension {
} }
} }
?> ?>

View File

@ -264,6 +264,85 @@ class DataObjectSet extends ViewableData implements IteratorAggregate {
return $ret; return $ret;
} }
/*
* Display a summarized pagination which limits the number of pages shown
* "around" the currently active page for visual balance.
* In case more paginated pages have to be displayed, only
*
* Example: 25 pages total, currently on page 6, context of 4 pages
* [prev] [1] ... [4] [5] [[6]] [7] [8] ... [25] [next]
*
* Example template usage:
* <code>
* <% if MyPages.MoreThanOnePage %>
* <% if MyPages.NotFirstPage %>
* <a class="prev" href="$MyPages.PrevLink">Prev</a>
* <% end_if %>
* <% control MyPages.PaginationSummary(4) %>
* <% if CurrentBool %>
* $PageNum
* <% else %>
* <% if Link %>
* <a href="$Link">$PageNum</a>
* <% else %>
* ...
* <% end_if %>
* <% end_if %>
* <% end_control %>
* <% if MyPages.NotLastPage %>
* <a class="next" href="$MyPages.NextLink">Next</a>
* <% end_if %>
* <% end_if %>
* </code>
*
* @param integer $context Number of pages to display "around" the current page. Number should be even,
* because its halved to either side of the current page.
* @return DataObjectSet
*/
public function PaginationSummary($context = 4) {
$ret = new DataObjectSet();
// convert number of pages to even number for offset calculation
if($context % 2) $context--;
// find out the offset
$current = $this->CurrentPage();
$totalPages = $this->TotalPages();
// if the first or last page is shown, use all content on one side (either left or right of current page)
// otherwise half the number for usage "around" the current page
$offset = ($current == 1 || $current == $totalPages) ? $context : floor($context/2);
$leftOffset = $current - ($offset);
if($leftOffset < 1) $leftOffset = 1;
if($leftOffset + $context > $totalPages) $leftOffset = $totalPages - $context;
for($i=0; $i < $totalPages; $i++) {
$link = HTTP::setGetVar($this->paginationGetVar, $i*$this->pageLength);
$num = $i+1;
$currentBool = ($current == $i+1) ? true:false;
if(
($num == $leftOffset-1 && $num != 1 && $num != $totalPages)
|| ($num == $leftOffset+$context+1 && $num != 1 && $num != $totalPages)
) {
$ret->push(new ArrayData(array(
"PageNum" => null,
"Link" => null,
"CurrentBool" => $currentBool,
)
));
} else if($num == 1 || $num == $totalPages || in_array($num, range($current-$offset,$current+$offset))) {
$ret->push(new ArrayData(array(
"PageNum" => $num,
"Link" => $link,
"CurrentBool" => $currentBool,
)
));
}
}
return $ret;
}
/** /**
* Returns true if the current page is not the first page. * Returns true if the current page is not the first page.
* @return boolean * @return boolean
@ -694,7 +773,7 @@ class DataObjectSet extends ViewableData implements IteratorAggregate {
$myViewer = SSViewer::fromString($currentTemplate); $myViewer = SSViewer::fromString($currentTemplate);
if(isset($nestingLevels[$level+1]['dataclass'])){ if(isset($nestingLevels[$level+1]['dataclass'])){
$childrenMethod = $nestingLevels[$level+1]['dataclass'];if($level==1){print_r($childrenMethod);die;} $childrenMethod = $nestingLevels[$level+1]['dataclass'];
} }
// sql-parts // sql-parts

View File

@ -236,6 +236,8 @@ abstract class Database extends Object {
$this->checkAndRepairTable($table); $this->checkAndRepairTable($table);
} }
$this->requireField($table, "ID", "int(11) not null auto_increment");
// Create custom fields // Create custom fields
if($fieldSchema) { if($fieldSchema) {
foreach($fieldSchema as $fieldName => $fieldSpec) { foreach($fieldSchema as $fieldName => $fieldSpec) {
@ -591,10 +593,11 @@ abstract class Query extends Object implements Iterator {
* @return array * @return array
*/ */
public function column() { public function column() {
$column = array();
foreach($this as $record) { foreach($this as $record) {
$column[] = reset($record); $column[] = reset($record);
} }
return isset($column) ? $column : null; return $column;
} }
/** /**
@ -603,6 +606,7 @@ abstract class Query extends Object implements Iterator {
* @return array * @return array
*/ */
public function keyedColumn() { public function keyedColumn() {
$column = array();
foreach($this as $record) { foreach($this as $record) {
$val = reset($record); $val = reset($record);
$column[$val] = $val; $column[$val] = $val;
@ -615,6 +619,7 @@ abstract class Query extends Object implements Iterator {
* @return array * @return array
*/ */
public function map() { public function map() {
$column = array();
foreach($this as $record) { foreach($this as $record) {
$key = reset($record); $key = reset($record);
$val = next($record); $val = next($record);

View File

@ -103,6 +103,7 @@ class ErrorPage extends Page {
$errorContent = $response->getBody(); $errorContent = $response->getBody();
// Check we have an assets base directory, creating if it we don't
if(!file_exists(ASSETS_PATH)) { if(!file_exists(ASSETS_PATH)) {
mkdir(ASSETS_PATH, 02775); mkdir(ASSETS_PATH, 02775);
} }
@ -113,6 +114,17 @@ class ErrorPage extends Page {
if($fh = fopen($filePath, "w")) { if($fh = fopen($filePath, "w")) {
fwrite($fh, $errorContent); fwrite($fh, $errorContent);
fclose($fh); fclose($fh);
} else {
$fileErrorText = sprintf(
_t(
"ErrorPage.ERRORFILEPROBLEM",
"Error opening file \"%s\" for writing. Please check file permissions."
),
$errorFile
);
FormResponse::status_message($fileErrorText, 'bad');
FormResponse::respond();
return;
} }
// Restore the version we're currently connected to. // Restore the version we're currently connected to.

View File

@ -159,7 +159,7 @@ class Hierarchy extends DataObjectDecorator {
protected function markingFinished() { protected function markingFinished() {
// Mark childless nodes as expanded. // Mark childless nodes as expanded.
foreach($this->markedNodes as $id => $node) { foreach($this->markedNodes as $id => $node) {
if(!$node->numChildren()) { if(!$node->isExpanded() && !$node->numChildren()) {
$node->markExpanded(); $node->markExpanded();
} }
} }
@ -351,7 +351,18 @@ class Hierarchy extends DataObjectDecorator {
* @return DataObjectSet * @return DataObjectSet
*/ */
public function Children() { public function Children() {
return $this->owner->stageChildren(false); if(!(isset($this->children) && $this->children)) {
$result = $this->owner->stageChildren(false);
if(isset($result)) {
$this->children = new DataObjectSet();
foreach($result as $child) {
if($child->canView()) {
$this->children->push($child);
}
}
}
}
return $this->children;
} }
/** /**

View File

@ -174,11 +174,12 @@ class MySQLDatabase extends Database {
public function createTable($tableName, $fields = null, $indexes = null) { public function createTable($tableName, $fields = null, $indexes = null) {
$fieldSchemas = $indexSchemas = ""; $fieldSchemas = $indexSchemas = "";
if(!isset($fields['ID'])) $fields['ID'] = "int(11) not null auto_increment";
if($fields) foreach($fields as $k => $v) $fieldSchemas .= "\"$k\" $v,\n"; if($fields) foreach($fields as $k => $v) $fieldSchemas .= "\"$k\" $v,\n";
if($indexes) foreach($indexes as $k => $v) $fieldSchemas .= $this->getIndexSqlDefinition($k, $v) . ",\n"; if($indexes) foreach($indexes as $k => $v) $fieldSchemas .= $this->getIndexSqlDefinition($k, $v) . ",\n";
$this->query("CREATE TABLE \"$tableName\" ( $this->query("CREATE TABLE \"$tableName\" (
ID int(11) not null auto_increment,
$fieldSchemas $fieldSchemas
$indexSchemas $indexSchemas
primary key (ID) primary key (ID)
@ -468,7 +469,7 @@ class MySQLDatabase extends Database {
//$parts=Array('datatype'=>'decimal', 'precision'=>"$this->wholeSize,$this->decimalSize"); //$parts=Array('datatype'=>'decimal', 'precision'=>"$this->wholeSize,$this->decimalSize");
//DB::requireField($this->tableName, $this->name, "decimal($this->wholeSize,$this->decimalSize)"); //DB::requireField($this->tableName, $this->name, "decimal($this->wholeSize,$this->decimalSize)");
return 'decimal(' . (int)$values['precision'] . ')'; return 'decimal(' . (int)$values['precision'] . ') not null';
} }
/** /**

View File

@ -727,5 +727,4 @@ class PDOQuery extends Query {
} }
} }
?> ?>

View File

@ -191,6 +191,42 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
*/ */
private static $runCMSFieldsExtensions = true; private static $runCMSFieldsExtensions = true;
/**
* Return a subclass map of SiteTree
* that shouldn't be hidden through
* {@link SiteTree::$hide_ancestor}
*
* @return array
*/
public static function page_type_classes() {
$classes = ClassInfo::getValidSubClasses();
array_shift($classes);
$kill_ancestors = array();
// figure out if there are any classes we don't want to appear
foreach($classes as $class) {
$instance = singleton($class);
// do any of the progeny want to hide an ancestor?
if($ancestor_to_hide = $instance->stat('hide_ancestor')) {
// note for killing later
$kill_ancestors[] = $ancestor_to_hide;
}
}
// If any of the descendents don't want any of the elders to show up, cruelly render the elders surplus to requirements.
if($kill_ancestors) {
$kill_ancestors = array_unique($kill_ancestors);
foreach($kill_ancestors as $mark) {
// unset from $classes
$idx = array_search($mark, $classes);
unset($classes[$idx]);
}
}
return $classes;
}
/** /**
* Get the URL for this page. * Get the URL for this page.
* *
@ -525,7 +561,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @todo Check we get a endless recursion if we use parent::can() * @todo Check we get a endless recursion if we use parent::can()
*/ */
function can($perm, $member = null) { function can($perm, $member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser(); if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
if($member && Permission::checkMember($member, "ADMIN")) return true; if($member && Permission::checkMember($member, "ADMIN")) return true;
@ -562,7 +598,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return boolean True if the current user can add children. * @return boolean True if the current user can add children.
*/ */
public function canAddChildren($member = null) { public function canAddChildren($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser(); if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
if($member && Permission::checkMember($member, "ADMIN")) return true; if($member && Permission::checkMember($member, "ADMIN")) return true;
// DEPRECATED 2.3: use canAddChildren() instead // DEPRECATED 2.3: use canAddChildren() instead
@ -593,7 +629,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return boolean True if the current user can view this page. * @return boolean True if the current user can view this page.
*/ */
public function canView($member = null) { public function canView($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser(); if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
// admin override // admin override
if($member && Permission::checkMember($member, "ADMIN")) return true; if($member && Permission::checkMember($member, "ADMIN")) return true;
@ -648,7 +684,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return boolean True if the current user can delete this page. * @return boolean True if the current user can delete this page.
*/ */
public function canDelete($member = null) { public function canDelete($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser(); if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
if($member && Permission::checkMember($member, "ADMIN")) return true; if($member && Permission::checkMember($member, "ADMIN")) return true;
@ -690,7 +726,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return boolean True if the current user can create pages on this class. * @return boolean True if the current user can create pages on this class.
*/ */
public function canCreate($member = null) { public function canCreate($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser(); if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
if($member && Permission::checkMember($member, "ADMIN")) return true; if($member && Permission::checkMember($member, "ADMIN")) return true;
@ -726,7 +762,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return boolean True if the current user can edit this page. * @return boolean True if the current user can edit this page.
*/ */
public function canEdit($member = null) { public function canEdit($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser(); if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
if($member && Permission::checkMember($member, "ADMIN")) return true; if($member && Permission::checkMember($member, "ADMIN")) return true;
@ -774,7 +810,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return boolean True if the current user can publish this page. * @return boolean True if the current user can publish this page.
*/ */
public function canPublish($member = null) { public function canPublish($member = null) {
if(!$member && $member !== FALSE) $member = Member::currentUser(); if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
if($member && Permission::checkMember($member, "ADMIN")) return true; if($member && Permission::checkMember($member, "ADMIN")) return true;
@ -816,7 +852,6 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
/** /**
* Return the title, description, keywords and language metatags. * Return the title, description, keywords and language metatags.
* *
* @todo Make generator tag dynamically determine version number (currently defaults to "2.0")
* @todo Move <title> tag in separate getter for easier customization and more obvious usage * @todo Move <title> tag in separate getter for easier customization and more obvious usage
* *
* @param boolean|string $includeTitle Show default <title>-tag, set to false for custom templating * @param boolean|string $includeTitle Show default <title>-tag, set to false for custom templating
@ -831,7 +866,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
? $this->MetaTitle ? $this->MetaTitle
: $this->Title) . "</title>\n"; : $this->Title) . "</title>\n";
} }
$tags .= "<meta name=\"generator\" http-equiv=\"generator\" content=\"SilverStripe - http://www.silverstripe.com\" />\n"; $version = new SapphireInfo();
$tags .= "<meta name=\"generator\" http-equiv=\"generator\" content=\"SilverStripe ". $version->Version() ." - http://www.silverstripe.com\" />\n";
$charset = ContentNegotiator::get_encoding(); $charset = ContentNegotiator::get_encoding();
$tags .= "<meta http-equiv=\"Content-type\" content=\"text/html; charset=$charset\" />\n"; $tags .= "<meta http-equiv=\"Content-type\" content=\"text/html; charset=$charset\" />\n";
@ -1076,26 +1113,6 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
Requirements::javascript(CMS_DIR . "/javascript/SitetreeAccess.js"); Requirements::javascript(CMS_DIR . "/javascript/SitetreeAccess.js");
Requirements::javascript(SAPPHIRE_DIR . '/javascript/UpdateURL.js'); Requirements::javascript(SAPPHIRE_DIR . '/javascript/UpdateURL.js');
// Backlink report
if($this->hasMethod('BackLinkTracking')) {
$links = $this->BackLinkTracking();
if($links->exists()) {
foreach($links as $link) {
$backlinks[] = "<li><a class=\"cmsEditlink\" href=\"admin/show/$link->ID\">" .
$link->Breadcrumbs(null,true) . "</a></li>";
}
$backlinks = "<div style=\"clear:left\">
" . _t('SiteTree.PAGESLINKING', 'The following pages link to this page:') .
"<ul>" . implode("",$backlinks) . "</ul></div>";
}
}
if(!isset($backlinks)) {
$backlinks = "<p>" . _t('SiteTree.NOBACKLINKS', 'This page hasn\'t been linked to from any pages.') . "</p>";
}
// Status / message // Status / message
// Create a status message for multiple parents // Create a status message for multiple parents
if($this->ID && is_numeric($this->ID)) { if($this->ID && is_numeric($this->ID)) {
@ -1139,18 +1156,37 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$message .= "NOTE: " . implode("<br />", $statusMessage); $message .= "NOTE: " . implode("<br />", $statusMessage);
} }
$backLinksNote = '';
$backLinksTable = new LiteralField('BackLinksNote', '<p>' . _t('NOBACKLINKEDPAGES', 'There are no pages linked to this page.') . '</p>');
// Create a table for showing pages linked to this one
if($this->BackLinkTracking() && $this->BackLinkTracking()->Count() > 0) {
$backLinksNote = new LiteralField('BackLinksNote', '<p>' . _t('SiteTree.PAGESLINKING', 'The following pages link to this page:') . '</p>');
$backLinksTable = new TableListField(
'BackLinkTracking',
'SiteTree',
array(
'Title' => 'Title'
),
'ChildID = ' . $this->ID,
'',
'LEFT JOIN SiteTree_LinkTracking ON SiteTree.ID = SiteTree_LinkTracking.SiteTreeID'
);
$backLinksTable->setFieldFormatting(array(
'Title' => '<a href=\"admin/show/$ID\">$Title</a>'
));
$backLinksTable->setPermissions(array(
'show',
'export'
));
}
// Lay out the fields // Lay out the fields
$fields = new FieldSet( $fields = new FieldSet(
new TabSet("Root", new TabSet("Root",
$tabContent = new TabSet('Content', $tabContent = new TabSet('Content',
$tabMain = new Tab('Main', $tabMain = new Tab('Main',
new TextField("Title", $this->fieldLabel('Title')), new TextField("Title", $this->fieldLabel('Title')),
/*new UniqueTextField("Title",
"Title",
"SiteTree",
"Another page is using that name. Page names should be unique.",
"Page Name"
),*/
new TextField("MenuTitle", $this->fieldLabel('MenuTitle')), new TextField("MenuTitle", $this->fieldLabel('MenuTitle')),
new HtmlEditorField("Content", _t('SiteTree.HTMLEDITORTITLE', "Content", PR_MEDIUM, 'HTML editor title')) new HtmlEditorField("Content", _t('SiteTree.HTMLEDITORTITLE', "Content", PR_MEDIUM, 'HTML editor title'))
), ),
@ -1206,8 +1242,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
new TextareaField("ToDo", "") new TextareaField("ToDo", "")
), ),
$tabReports = new TabSet('Reports', $tabReports = new TabSet('Reports',
$tabBacklinks =new Tab('Backlinks', $tabBacklinks = new Tab('Backlinks',
new LiteralField("Backlinks", $backlinks) $backLinksNote,
$backLinksTable
) )
), ),
$tabAccess = new Tab('Access', $tabAccess = new Tab('Access',
@ -1496,10 +1533,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return array * @return array
*/ */
protected function getClassDropdown() { protected function getClassDropdown() {
$classes = ClassInfo::getValidSubClasses('SiteTree'); $classes = self::page_type_classes();
array_shift($classes);
$currentClass = null; $currentClass = null;
$result = array();
$result = array(); $result = array();
foreach($classes as $class) { foreach($classes as $class) {

View File

@ -617,6 +617,27 @@ class Versioned extends DataObjectDecorator {
return $version; return $version;
} }
/**
* Pre-populate the cache for Versioned::get_versionnumber_by_stage() for a list of record IDs,
* for more efficient database querying. If $idList is null, then every page will be pre-cached.
*/
static function prepopulate_versionnumber_cache($class, $stage, $idList = null) {
$filter = "";
if($idList) {
// Validate the ID list
foreach($idList as $id) if(!is_numeric($id)) user_error("Bad ID passed to Versioned::prepopulate_versionnumber_cache() in \$idList: " . $id, E_USER_ERROR);
$filter = "WHERE ID IN(" .implode(", ", $idList) . ")";
}
$baseClass = ClassInfo::baseDataClass($class);
$stageTable = ($stage == 'Stage') ? $baseClass : "{$baseClass}_{$stage}";
$versions = DB::query("SELECT ID, Version FROM `$stageTable` $filter")->map();
foreach($versions as $id => $version) {
self::$cache_versionnumber[$baseClass][$stage][$id] = $version;
}
}
/** /**
* Get a set of class instances by the given stage. * Get a set of class instances by the given stage.
* *

View File

@ -64,7 +64,8 @@ abstract class DBField extends ViewableData {
} }
/** /**
* Returns the name of this field * Returns the name of this field.
* @return string
*/ */
function getName() { function getName() {
return $this->name; return $this->name;

View File

@ -43,6 +43,10 @@ class Decimal extends DBField {
return new NumericField($this->name, $title); return new NumericField($this->name, $title);
} }
public function nullValue() {
return "0.00";
}
/** /**
* Return an encoding of the given value suitable for inclusion in a SQL statement. * Return an encoding of the given value suitable for inclusion in a SQL statement.
* If necessary, this should include quotes. * If necessary, this should include quotes.

View File

@ -6,6 +6,10 @@
*/ */
class SSDatetime extends Date { class SSDatetime extends Date {
function setValue($value) { function setValue($value) {
// Default to NZ date format - strtotime expects a US date
if(ereg('^([0-9]+)/([0-9]+)/([0-9]+)$', $value, $parts))
$value = "$parts[2]/$parts[1]/$parts[3]";
if($value) $this->value = date('Y-m-d H:i:s', strtotime($value)); if($value) $this->value = date('Y-m-d H:i:s', strtotime($value));
else $value = null; else $value = null;
} }

View File

@ -50,10 +50,6 @@ class Varchar extends DBField {
return str_replace("\n", '\par ', $this->value); return str_replace("\n", '\par ', $this->value);
} }
/*function forTemplate() {
return $this->raw2HTML();
}*/
function LimitCharacters($limit = 20, $add = "...") { function LimitCharacters($limit = 20, $add = "...") {
$value = trim($this->value); $value = trim($this->value);
return (strlen($value) > $limit) ? substr($value, 0, $limit) . $add : $value; return (strlen($value) > $limit) ? substr($value, 0, $limit) . $add : $value;

View File

@ -9,6 +9,10 @@
width: 200px; width: 200px;
} }
#SiteTreeFilterDate {
width: 68px;
}
.calendardate img { .calendardate img {
position: relative; position: relative;
top: 2px; top: 2px;
@ -17,11 +21,16 @@
.calendarpopup { .calendarpopup {
position: absolute; position: absolute;
left: 13.6em; left: 9em;
_left: 1.3em;
top: -0.15em; top: -0.15em;
_top: 4em;
display: none; display: none;
z-index: 2; z-index: 2;
} }
.calendarpopup.focused { .calendarpopup.focused {
display: block; display: block;
} }

View File

@ -1,15 +1,17 @@
html,body {
overflow:auto !important;
background: #fff !important;
}
.ComplexTableField_popup { .ComplexTableField_popup {
background: #fff; background: #fff;
} }
html {
overflow-y: auto !important;
}
body {
height: auto;
}
#ComplexTableField_Popup_DetailForm input.loading { #ComplexTableField_Popup_DetailForm input.loading {
background: #fff url(../../cms/images/network-save.gif) left center no-repeat; background: #fff url(../../cms/images/network-save.gif) left center no-repeat;
padding-left:16px; padding-left: 16px;
} }
.PageControls { .PageControls {

View File

@ -138,3 +138,23 @@ form .message {
width:240px; width:240px;
border-color: #FF4040; border-color: #FF4040;
} }
/** LOGIN FORM **/
#Remember {
margin: 0.5em 0 0.5em 11em !important;
}
p#Remember label {
display: inline-block;
margin: 0;
}
#Remember input {
float: left;
margin: 0 5px 0 0;
}
#MemberLoginForm_LoginForm .Actions {
padding-left: 12em;
}
#ForgotPassword {
margin-top: 1em;
}

View File

@ -1,8 +1,3 @@
/* HACK Doesn't work in an iframe-popup at the moment, so we disable it */
#Avatar {
display: none;
}
#i18nStatus { #i18nStatus {
margin-left: 0; margin-left: 0;
} }

View File

@ -4,24 +4,25 @@
bottom: 0; bottom: 0;
left: 0; left: 0;
width: 100%; width: 100%;
border-top: 3px solid #d4d0c8; border-top: 2px solid #d4d0c8;
background-color:#81858d; background-color:#81858d;
height: 18px; height: 22px;
overflow:hidden; overflow:hidden;
background-image:url(../../cms/images/textures/bottom.png); background: #4d4e5a url(../../cms/images/textures/footerBg.gif) repeat-x left top;
}
#SilverStripeNavigator * {
font-family: Arial,Helvetica,sans-serif;
font-size: 10px !important;
} }
#SilverStripeNavigator .holder { #SilverStripeNavigator .holder {
text-align: center; text-align: center;
padding-top : 3px; padding-top : 4px;
padding-left : 3px; padding-left : 3px;
padding-right : 6px; padding-right : 6px;
font-size: 10px;
color: white; color: white;
border-top: 1px solid #555555; border-top: 1px solid #555555;
} }
#SilverStripeNavigator #logInStatus { #SilverStripeNavigator #logInStatus {
float: right; float: right;
@ -32,72 +33,52 @@
} }
#SilverStripeNavigator a { #SilverStripeNavigator a {
color: #333; color: #fff;
background-color: transparent;
text-decoration: none;
}
#SilverStripeNavigator a:hover {
color: #333;
background-color: transparent; background-color: transparent;
text-decoration: underline; text-decoration: underline;
} }
#SilverStripeNavigator a:hover {
background-color: transparent;
}
#SilverStripeNavigator .bottomTabs a { #SilverStripeNavigator .bottomTabs a {
width: auto; margin-right: 8px;
display: block; text-decoration: underline;
float : left;
height : 13px;
padding-left : 12px;
padding-right : 12px;
position:relative;
top : -3px;
border : 1px solid #65686e;
border-top : none;
cursor:pointer;
background-color: #cdc9c1;
color : #333333;
background-image: none;
} }
#SilverStripeNavigator .bottomTabs div.blank { #SilverStripeNavigator .bottomTabs div.blank {
display: block; display: block;
float : left; float : left;
height : 13px; height : 13px;
padding-left : 12px;
padding-right : 12px;
position:relative; position:relative;
top : -3px; top : -2px;
border : 1px solid #65686e; cursor: pointer;
border-top : none;
cursor:pointer;
background-color: #cdc9c1;
color : #333333;
border : none; border : none;
background-color: transparent; background-color: transparent;
padding-right: 2px; padding: 2px 4px 2px 2px;
padding-left: 2px; font-weight: bold;
padding-top : 2px;
color:#FFFFFF;
} }
#SilverStripeNavigator .bottomTabs a.current { #SilverStripeNavigator .bottomTabs a.current {
background-color : #d4d0c8;
padding-top : 1px;
top : -5px;
height : 15px;
font-weight:bold; font-weight:bold;
font-size : 11px; text-decoration: none;
border : 1px solid #555555;
} }
#SilverStripeNavigatorMessage { #SilverStripeNavigatorMessage {
font-family: 'Lucida Grande', Verdana, Arial, 'sans-serif';
position: absolute; position: absolute;
right: 45%; right: 20px;
top: 10px; top: 40px;
padding: 10px; padding: 10px;
border-color: #c99; border-color: #c99;
color: #fff; color: #fff;
background-color: #c00; background-color: #c00;
border: 1px solid #000;
}
#SilverStripeNavigator #logInStatus {
background:transparent url(../../cms/images/logout.gif) no-repeat scroll right top !important;
padding-bottom:4px;
padding-right:20px;
} }

View File

@ -8,7 +8,7 @@ table.CMSList {
width : 100%; width : 100%;
} }
/* HACK Preventing IE6 from showing double borders */ /* Preventing IE6 from showing double borders */
body>div table.TableField, body>div table.TableField,
body>div table.TableListField, body>div table.TableListField,
body>div .TableListField table.data, body>div .TableListField table.data,
@ -113,6 +113,7 @@ table.CMSList tbody td.checkbox {
table.TableField tbody tr.over td, table.TableField tbody tr.over td,
.TableListField table.data tbody tr.over td, .TableListField table.data tbody tr.over td,
.TableListField table.data tbody tr.over td input,
table.CMSList tbody td.over td{ table.CMSList tbody td.over td{
background-color: #FFCC66; background-color: #FFCC66;
} }

View File

@ -93,7 +93,14 @@ abstract class BulkLoader extends ViewableData {
* Specifies how to determine duplicates based on one or more provided fields * Specifies how to determine duplicates based on one or more provided fields
* in the imported data, matching to properties on the used {@link DataObject} class. * in the imported data, matching to properties on the used {@link DataObject} class.
* Alternatively the array values can contain a callback method (see example for * Alternatively the array values can contain a callback method (see example for
* implementation details). * implementation details). The callback method should be defined on the source class.
*
* NOTE: If you're trying to get a unique Member record by a particular field that
* isn't Email, you need to ensure that Member is correctly set to the unique field
* you want, as it will merge any duplicates during {@link Member::onBeforeWrite()}.
*
* {@see Member::set_unique_identifier_field()}.
*
* If multiple checks are specified, the first one "wins". * If multiple checks are specified, the first one "wins".
* *
* <code> * <code>
@ -222,7 +229,7 @@ abstract class BulkLoader extends ViewableData {
* @return boolean * @return boolean
*/ */
protected function isNullValue($val, $fieldName = null) { protected function isNullValue($val, $fieldName = null) {
return (empty($val)); return (empty($val) && $val !== '0');
} }
} }

View File

@ -171,5 +171,4 @@ class ModelViewer_Relation extends ViewableData {
} }
?> ?>

View File

@ -262,6 +262,22 @@ class Email extends ViewableData {
$this->body; $this->body;
} }
/**
* Set template name (without *.ss extension).
*
* @param string $template
*/
public function setTemplate($template) {
$this->ss_template = $template;
}
/**
* @return string
*/
public function getTemplate() {
return $this->ss_template;
}
protected function templateData() { protected function templateData() {
if($this->template_data) { if($this->template_data) {
return $this->template_data->customise(array( return $this->template_data->customise(array(
@ -385,6 +401,8 @@ class Email extends ViewableData {
$headers['Bcc'] .= self::$bcc_all_emails_to; $headers['Bcc'] .= self::$bcc_all_emails_to;
} }
Requirements::restore();
return self::mailer()->sendPlain($to, $this->from, $subject, $this->body, $this->attachments, $headers); return self::mailer()->sendPlain($to, $this->from, $subject, $this->body, $this->attachments, $headers);
} }
@ -543,12 +561,19 @@ class Email extends ViewableData {
* *
* @param string $email Email-address * @param string $email Email-address
* @param string $method Method for obfuscating/encoding the address * @param string $method Method for obfuscating/encoding the address
* - 'direction': Reverse the text and then use CSS to put the text direction back to normal
* - 'visible': Simple string substitution ('@' to '[at]', '.' to '[dot], '-' to [dash]) * - 'visible': Simple string substitution ('@' to '[at]', '.' to '[dot], '-' to [dash])
* - 'hex': Hexadecimal URL-Encoding - useful for mailto: links * - 'hex': Hexadecimal URL-Encoding - useful for mailto: links
* @return string * @return string
*/ */
public static function obfuscate($email, $method = 'visible') { public static function obfuscate($email, $method = 'visible') {
switch($method) { switch($method) {
case 'direction' :
Requirements::customCSS(
'span.codedirection { unicode-bidi: bidi-override; direction: rtl; }',
'codedirectionCSS'
);
return '<span class="codedirection">' . strrev($email) . '</span>';
case 'visible' : case 'visible' :
$obfuscated = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] '); $obfuscated = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
return strtr($email, $obfuscated); return strtr($email, $obfuscated);
@ -714,38 +739,21 @@ class Email_BounceRecord extends DataObject {
static $has_one = array( static $has_one = array(
'Member' => 'Member' 'Member' => 'Member'
); );
}
/** static $has_many = array();
* This class is responsible for ensuring that members who are on it receive NO email
* communication at all. any correspondance is caught before the email is sent.
* @package sapphire
* @subpackage email
*/
class Email_BlackList extends DataObject{
static $db = array(
'BlockedEmail' => 'Varchar',
);
static $has_one = array(
'Member' => 'Member'
);
/** static $many_many = array();
* Helper function to see if the email being
* sent has specifically been blocked. static $defaults = array();
*/
static function isBlocked($email){
$blockedEmails = DataObject::get("Email_BlackList")->toDropDownMap("ID","BlockedEmail"); /**
if($blockedEmails){ * a record of Email_BounceRecord can't be created manually. Instead, it should be
if(in_array($email,$blockedEmails)){ * created though system.
return true; */
}else{ public function canCreate($member = null) {
return false; return false;
} }
}else{
return false;
}
}
} }
?> ?>

View File

@ -17,7 +17,7 @@ class Mailer extends Object {
* Send a plain-text email * Send a plain-text email
*/ */
function sendPlain($to, $from, $subject, $plainContent, $attachedFiles = false, $customheaders = false) { function sendPlain($to, $from, $subject, $plainContent, $attachedFiles = false, $customheaders = false) {
return plaintextEmail($to, $from, $subject, $htmlContent, $attachedFiles, $customheaders); return plaintextEmail($to, $from, $subject, $plainContent, $attachedFiles, $customheaders);
} }
/** /**
@ -428,4 +428,3 @@ function loadMimeTypes() {
$global_mimetypes = $mimeData; $global_mimetypes = $mimeData;
return $mimeData; return $mimeData;
} }

View File

@ -17,6 +17,12 @@ class QueuedEmail extends DataObject {
'To' => 'Member' 'To' => 'Member'
); );
static $has_many = array();
static $many_many = array();
static $defaults = array();
// overwrite this method to provide a check whether or not to send the email // overwrite this method to provide a check whether or not to send the email
function canSendEmail() { function canSendEmail() {
return true; return true;

View File

@ -33,14 +33,21 @@ class File extends DataObject {
"Owner" => "Member" "Owner" => "Member"
); );
static $extensions = array( static $has_many = array();
"Hierarchy",
); static $many_many = array();
static $belongs_many_many = array( static $belongs_many_many = array(
"BackLinkTracking" => "SiteTree", "BackLinkTracking" => "SiteTree",
); );
static $defaults = array();
static $extensions = array(
"Hierarchy",
);
/** /**
* Cached result of a "SHOW FIELDS" call * Cached result of a "SHOW FIELDS" call
* in instance_get() for performance reasons. * in instance_get() for performance reasons.

View File

@ -441,7 +441,7 @@ class Folder extends File {
*/ */
function getUploadIframe() { function getUploadIframe() {
return <<<HTML return <<<HTML
<iframe name="AssetAdmin_upload" src="admin/assets/uploadiframe/{$this->ID}" id="AssetAdmin_upload" border="0" style="border-style: none; width: 100%; height: 200px"> <iframe name="AssetAdmin_upload" src="admin/assets/uploadiframe/{$this->ID}" id="AssetAdmin_upload" border="0" style="border-style none !important; width: 97%; min-height: 300px; height: 100%; height: expression(document.body.clientHeight) !important;">
</iframe> </iframe>
HTML; HTML;
} }

View File

@ -4,6 +4,15 @@
* *
* ASSUMPTION -> IF you pass your source as an array, you pass values as an array too. * ASSUMPTION -> IF you pass your source as an array, you pass values as an array too.
* Likewise objects are handled the same. * Likewise objects are handled the same.
*
* @todo Document the different source data that can be used
* with this form field - e.g ComponentSet, DataObjectSet,
* array. Is it also appropriate to accept so many different
* types of data when just using an array would be appropriate?
*
* @todo Make use of FormField->createTag() to generate the
* HTML tag(s) for this field.
*
* @package forms * @package forms
* @subpackage fields-basic * @subpackage fields-basic
*/ */
@ -11,11 +20,12 @@ class CheckboxSetField extends OptionsetField {
protected $disabled = false; protected $disabled = false;
/** /**
* Object handles arrays and dosets being passed by reference. * @todo Explain different source data that can be used with this field,
* * e.g. SQLMap, DataObjectSet or an array.
* @todo Should use CheckboxField FieldHolder rather than constructing own markup. *
*/ * @todo Should use CheckboxField FieldHolder rather than constructing own markup.
*/
function Field() { function Field() {
Requirements::css(SAPPHIRE_DIR . '/css/CheckboxSetField.css'); Requirements::css(SAPPHIRE_DIR . '/css/CheckboxSetField.css');
@ -86,10 +96,10 @@ class CheckboxSetField extends OptionsetField {
$checked = ''; $checked = '';
if(isset($items)) { if(isset($items)) {
in_array($key,$items) ? $checked = " checked=\"checked\"" : $checked = ""; $checked = (in_array($key, $items)) ? ' checked="checked"' : '';
} }
$this->disabled ? $disabled = " disabled=\"disabled\"" : $disabled = ""; $disabled = ($this->disabled) ? $disabled = ' disabled="disabled"' : '';
$options .= "<li class=\"$extraClass\"><input id=\"$itemID\" name=\"$this->name[$key]\" type=\"checkbox\" value=\"$key\"$checked $disabled class=\"checkbox\" /> <label for=\"$itemID\">$value</label></li>\n"; $options .= "<li class=\"$extraClass\"><input id=\"$itemID\" name=\"$this->name[$key]\" type=\"checkbox\" value=\"$key\"$checked $disabled class=\"checkbox\" /> <label for=\"$itemID\">$value</label></li>\n";
} }
@ -108,7 +118,7 @@ class CheckboxSetField extends OptionsetField {
if(!$value && $obj && $obj instanceof DataObject && $obj->hasMethod($this->name)) { if(!$value && $obj && $obj instanceof DataObject && $obj->hasMethod($this->name)) {
$funcName = $this->name; $funcName = $this->name;
$selected = $obj->$funcName(); $selected = $obj->$funcName();
$value = $selected->toDropdownMap('ID','ID'); $value = $selected->toDropdownMap('ID', 'ID');
} }
parent::setValue($value, $obj); parent::setValue($value, $obj);
@ -133,7 +143,7 @@ class CheckboxSetField extends OptionsetField {
$record->$fieldname()->setByIDList($idList); $record->$fieldname()->setByIDList($idList);
} elseif($fieldname && $record) { } elseif($fieldname && $record) {
if($this->value) { if($this->value) {
$this->value = str_replace(",", "{comma}", $this->value); $this->value = str_replace(',', '{comma}', $this->value);
$record->$fieldname = implode(",", $this->value); $record->$fieldname = implode(",", $this->value);
} else { } else {
$record->$fieldname = ''; $record->$fieldname = '';
@ -142,79 +152,93 @@ class CheckboxSetField extends OptionsetField {
} }
/** /**
* Return the CheckboxSetField value, as an array of the selected item keys * Return the CheckboxSetField value as an array
* selected item keys.
*
* @return string
*/ */
function dataValue() { function dataValue() {
if($this->value&&is_array($this->value)){ if($this->value && is_array($this->value)) {
// Filter items to those who aren't 0
$filtered = array(); $filtered = array();
foreach($this->value as $item) if($item) $filtered[] = str_replace(",", "{comma}", $item); foreach($this->value as $item) {
return implode(",", $filtered); if($item) {
} else { $filtered[] = str_replace(",", "{comma}", $item);
return ''; }
}
return implode(',', $filtered);
} }
return '';
} }
function performDisabledTransformation() { function performDisabledTransformation() {
$clone = clone $this; $clone = clone $this;
$clone->setDisabled(true); $clone->setDisabled(true);
return $clone; return $clone;
} }
/** /**
* Makes a pretty readonly field * Transforms the source data for this CheckboxSetField
*/ * into a comma separated list of values.
*
* @return ReadonlyField
*/
function performReadonlyTransformation() { function performReadonlyTransformation() {
$values = ''; $values = '';
$data = array();
$items = $this->value; $items = $this->value;
foreach($this->source as $source) { if($this->source) {
if(is_object($source)) { foreach($this->source as $source) {
$sourceTitles[$source->ID] = $source->Title; if(is_object($source)) {
$sourceTitles[$source->ID] = $source->Title;
}
} }
} }
if($items){ if($items) {
// Items is a DO Set // Items is a DO Set
if(is_a($items,'DataObjectSet')){ if(is_a($items, 'DataObjectSet')) {
foreach($items as $item) {
foreach($items as $item){
$data[] = $item->Title; $data[] = $item->Title;
} }
if($data) { if($data) $values = implode(', ', $data);
$values = implode(", ",$data);
}
// Items is an array or single piece of string (including comma seperated string) // Items is an array or single piece of string (including comma seperated string)
}else{ } else {
if(!is_array($items)) { if(!is_array($items)) {
$items = split(" *, *", trim($items)); $items = split(' *, *', trim($items));
} }
foreach($items as $item){
foreach($items as $item) {
if(is_array($item)) { if(is_array($item)) {
$data[] = $item['Title']; $data[] = $item['Title'];
} else if(is_array($this->source) && !empty($this->source[$item])) { } elseif(is_array($this->source) && !empty($this->source[$item])) {
$data[] = $this->source[$item]; $data[] = $this->source[$item];
} else if(is_a($this->source, "ComponentSet")){ } elseif(is_a($this->source, 'ComponentSet')) {
//added for editable checkboxset.
$data[] = $sourceTitles[$item]; $data[] = $sourceTitles[$item];
} else { } else {
$data[] = $item; $data[] = $item;
} }
} }
$values = implode(", ",$data);
$values = implode(', ', $data);
} }
} }
$field = new ReadonlyField($this->name,$this->title ? $this->title : "",$values); $title = ($this->title) ? $this->title : '';
$field = new ReadonlyField($this->name, $title, $values);
$field->setForm($this->form); $field->setForm($this->form);
return $field; return $field;
} }
function ExtraOptions() { function ExtraOptions() {
return FormField::ExtraOptions(); return FormField::ExtraOptions();
} }
} }
?> ?>

View File

@ -472,7 +472,7 @@ JS;
if(!$childData->ID && $this->getParentClass()) { if(!$childData->ID && $this->getParentClass()) {
// make sure the relation-link is existing, even if we just add the sourceClass and didn't save it // make sure the relation-link is existing, even if we just add the sourceClass and didn't save it
$parentIDName = $this->getParentIdName( $this->getParentClass(), $this->sourceClass() ); $parentIDName = $this->getParentIdName( $this->getParentClass(), $this->sourceClass() );
$childData->$parentIDName = $childData->ID; $childData->$parentIDName = $this->sourceID();
} }
$detailFields = $this->getCustomFieldsFor($childData); $detailFields = $this->getCustomFieldsFor($childData);
@ -725,8 +725,10 @@ class ComplexTableField_ItemRequest extends RequestHandler {
* @see Form::ReferencedField * @see Form::ReferencedField
*/ */
function saveComplexTableField($data, $form, $request) { function saveComplexTableField($data, $form, $request) {
$form->saveInto($this->dataObj()); $dataObject = $this->dataObj();
$this->dataObj()->write();
$form->saveInto($dataObject);
$dataObject->write();
$closeLink = sprintf( $closeLink = sprintf(
'<small><a href="' . $_SERVER['HTTP_REFERER'] . '" onclick="javascript:window.top.GB_hide(); return false;">(%s)</a></small>', '<small><a href="' . $_SERVER['HTTP_REFERER'] . '" onclick="javascript:window.top.GB_hide(); return false;">(%s)</a></small>',
@ -734,8 +736,8 @@ class ComplexTableField_ItemRequest extends RequestHandler {
); );
$message = sprintf( $message = sprintf(
_t('ComplexTableField.SUCCESSEDIT', 'Saved %s %s %s'), _t('ComplexTableField.SUCCESSEDIT', 'Saved %s %s %s'),
$this->dataObj()->singular_name(), $dataObject->singular_name(),
'<a href="' . $this->Link() . '">"' . $this->dataObj()->Title . '"</a>', '<a href="' . $this->Link() . '">"' . $dataObject->Title . '"</a>',
$closeLink $closeLink
); );
$form->sessionMessage($message, 'good'); $form->sessionMessage($message, 'good');
@ -754,7 +756,8 @@ class ComplexTableField_ItemRequest extends RequestHandler {
return null; return null;
} }
$item = $this->unpagedSourceItems->First(); // We never use $item afterwards in the function, where we have it here? disable it!
//$item = $this->unpagedSourceItems->First();
$start = 0; $start = 0;
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}"); return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
} }
@ -764,7 +767,8 @@ class ComplexTableField_ItemRequest extends RequestHandler {
return null; return null;
} }
$item = $this->unpagedSourceItems->Last(); // We never use $item afterwards in the function, where we have it here? disable it!
// $item = $this->unpagedSourceItems->Last();
$start = $this->totalCount - 1; $start = $this->totalCount - 1;
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}"); return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
} }
@ -774,7 +778,8 @@ class ComplexTableField_ItemRequest extends RequestHandler {
return null; return null;
} }
$item = $this->unpagedSourceItems->getIterator()->getOffset($_REQUEST['ctf']['start'] + 1); // We never use $item afterwards in the function, where we have it here? disable it!
//$item = $this->unpagedSourceItems->getIterator()->getOffset($_REQUEST['ctf']['start'] + 1);
$start = $_REQUEST['ctf']['start'] + 1; $start = $_REQUEST['ctf']['start'] + 1;
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}"); return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
@ -785,7 +790,8 @@ class ComplexTableField_ItemRequest extends RequestHandler {
return null; return null;
} }
$item = $this->unpagedSourceItems->getIterator()->getOffset($_REQUEST['ctf']['start'] - 1); // We never use $item afterwards in the function, where we have it here? disable it!
//$item = $this->unpagedSourceItems->getIterator()->getOffset($_REQUEST['ctf']['start'] - 1);
$start = $_REQUEST['ctf']['start'] - 1; $start = $_REQUEST['ctf']['start'] - 1;
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}"); return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");

View File

@ -112,15 +112,16 @@ class DateField_Disabled extends DateField {
$df->setValue($this->dataValue()); $df->setValue($this->dataValue());
if(date('Y-m-d', time()) == $this->dataValue()) { if(date('Y-m-d', time()) == $this->dataValue()) {
$val = Convert::raw2xml($this->value . ' ('._t('DateField.TODAY','today').')'); $val = Convert::raw2xml($this->value . ' ('._t('DateField.TODAY','today').')');
} else { } else {
$val = Convert::raw2xml($this->value . ', ' . $df->Ago()); $val = Convert::raw2xml($this->value . ', ' . $df->Ago());
} }
} else { } else {
$val = '<i>('._t('DateField.NOTSET', 'not set').')</i>'; $val = '<i>('._t('DateField.NOTSET', 'not set').')</i>';
} }
return "<span class=\"readonly\" id=\"" . $this->id() . "\">$val</span>"; return "<span class=\"readonly\" id=\"" . $this->id() . "\">$val</span>
<input type=\"hidden\" value=\"{$this->value}\" name=\"$this->name\" />";
} }
function Type() { function Type() {

View File

@ -83,7 +83,22 @@ class FieldGroup extends CompositeField {
} }
function FieldHolder() { function FieldHolder() {
return FormField::FieldHolder(); $Title = $this->XML_val('Title');
$Message = $this->XML_val('Message');
$MessageType = $this->XML_val('MessageType');
$RightTitle = $this->XML_val('RightTitle');
$Type = $this->XML_val('Type');
$extraClass = $this->XML_val('extraClass');
$Name = $this->XML_val('Name');
$Field = $this->XML_val('Field');
$titleBlock = (!empty($Title)) ? "<label class=\"left\">$Title</label>" : "";
$messageBlock = (!empty($Message)) ? "<span class=\"message $MessageType\">$Message</span>" : "";
$rightTitleBlock = (!empty($RightTitle)) ? "<label class=\"right\">$RightTitle</label>" : "";
return <<<HTML
<div id="$Name" class="field $Type $extraClass">$titleBlock<div class="middleColumn">$Field</div>$rightTitleBlock$messageBlock</div>
HTML;
} }
function Message() { function Message() {

View File

@ -201,7 +201,7 @@ class FileField extends FormField {
* @return string * @return string
*/ */
public function getFolderName() { public function getFolderName() {
return $folderName; return $this->folderName;
} }
public function validate($validator) { public function validate($validator) {

View File

@ -886,6 +886,8 @@ class Form extends RequestHandler {
*/ */
function getData() { function getData() {
$dataFields = $this->fields->dataFields(); $dataFields = $this->fields->dataFields();
$data = array();
if($dataFields){ if($dataFields){
foreach($dataFields as $field) { foreach($dataFields as $field) {
if($field->Name()) { if($field->Name()) {

View File

@ -69,6 +69,10 @@ class FormAction extends FormField {
'type' => 'submit', 'type' => 'submit',
'name' => $this->action 'name' => $this->action
); );
if($this->isReadonly()) {
$attributes['disabled'] = 'disabled';
$attributes['class'] = $attributes['class'] . ' disabled';
}
return $this->createTag('button', $attributes, $this->attrTitle()); return $this->createTag('button', $attributes, $this->attrTitle());
} else { } else {
@ -79,7 +83,10 @@ class FormAction extends FormField {
'name' => $this->action, 'name' => $this->action,
'value' => ($this->dontEscape) ? $this->Title() : $this->attrTitle() 'value' => ($this->dontEscape) ? $this->Title() : $this->attrTitle()
); );
if($this->isReadonly()) {
$attributes['disabled'] = 'disabled';
$attributes['class'] = $attributes['class'] . ' disabled';
}
$attributes['title'] = ($this->description) ? $this->description : ($this->dontEscape) ? $this->Title() : $this->attrTitle(); $attributes['title'] = ($this->description) ? $this->description : ($this->dontEscape) ? $this->Title() : $this->attrTitle();
return $this->createTag('input', $attributes); return $this->createTag('input', $attributes);

View File

@ -481,7 +481,8 @@ HTML;
* *
* @todo shouldn't this be an abstract method? * @todo shouldn't this be an abstract method?
*/ */
function jsValidation() {} function jsValidation() {
}
/** /**
* Validation Functions for each field type by default * Validation Functions for each field type by default
@ -489,7 +490,9 @@ HTML;
* *
* @todo shouldn't this be an abstract method? * @todo shouldn't this be an abstract method?
*/ */
function validate(){return true;} function validate() {
return true;
}
/** /**
* Describe this field, provide help text for it. * Describe this field, provide help text for it.

View File

@ -47,52 +47,6 @@ class HasManyComplexTableField extends ComplexTableField {
elseif($this->controller instanceof ContentController) return $this->controller->data()->class; elseif($this->controller instanceof ContentController) return $this->controller->data()->class;
} }
function getQuery($limitClause = null) {
if($this->customQuery) {
$query = $this->customQuery;
$query->select[] = "{$this->sourceClass}.ID AS ID";
$query->select[] = "{$this->sourceClass}.ClassName AS ClassName";
$query->select[] = "{$this->sourceClass}.ClassName AS \"RecordClassName\"";
}
else {
$query = singleton($this->sourceClass)->extendedSQL($this->sourceFilter, $this->sourceSort, $limitClause, $this->sourceJoin);
// Add more selected fields if they are from joined table.
$SNG = singleton($this->sourceClass);
foreach($this->FieldList() as $k => $title) {
if(! $SNG->hasField($k) && ! $SNG->hasMethod('get' . $k))
$query->select[] = $k;
}
}
return clone $query;
}
function sourceItems() {
if($this->sourceItems) return $this->sourceItems;
$limitClause = '';
if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) {
$limitClause = $_REQUEST[ 'ctf' ][ $this->Name() ][ 'start' ] . ", $this->pageSize";
} else {
$limitClause = "0, $this->pageSize";
}
$dataQuery = $this->getQuery($limitClause);
$records = $dataQuery->execute();
$items = new DataObjectSet();
$sourceClass = $this->sourceClass;
$dataobject = new $sourceClass();
$items = $dataobject->buildDataObjectSet($records, 'DataObjectSet');
$this->unpagedSourceItems = $dataobject->buildDataObjectSet($records, 'DataObjectSet');
$this->totalCount = ($this->unpagedSourceItems) ? $this->unpagedSourceItems->TotalItems() : null;
return $items;
}
function getControllerID() { function getControllerID() {
return $this->controller->ID; return $this->controller->ID;
} }
@ -123,15 +77,21 @@ class HasManyComplexTableField extends ComplexTableField {
return $this->addTitle ? $this->addTitle : parent::Title(); return $this->addTitle ? $this->addTitle : parent::Title();
} }
/**
* Get the IDs of the selected items, in a has_many or many_many relation
*/
function selectedItemIDs() {
$fieldName = $this->name;
$selectedItems = $this->form->getRecord()->$fieldName();
$itemIDs = array();
foreach($selectedItems as $item) $itemIDs[] = $item->ID;
return $itemIDs;
}
function ExtraData() { function ExtraData() {
$items = array(); $items = array();
if($this->unpagedSourceItems) {
foreach($this->unpagedSourceItems as $item) { $list = implode(',', $this->selectedItemIDs());
if($item->{$this->joinField} == $this->controller->ID)
$items[] = $item->ID;
}
}
$list = implode(',', $items);
$inputId = $this->id() . '_' . $this->htmlListEndName; $inputId = $this->id() . '_' . $this->htmlListEndName;
return <<<HTML return <<<HTML
<input id="$inputId" name="{$this->name}[{$this->htmlListField}]" type="hidden" value="$list"/> <input id="$inputId" name="{$this->name}[{$this->htmlListField}]" type="hidden" value="$list"/>

View File

@ -15,7 +15,7 @@ class HtmlEditorField extends TextareaField {
/** /**
* Construct a new HtmlEditor field * Construct a new HtmlEditor field
*/ */
function __construct($name, $title = "", $rows = 20, $cols = 20, $value = "", $form = null) { function __construct($name, $title = null, $rows = 20, $cols = 20, $value = "", $form = null) {
parent::__construct($name, $title, $rows, $cols, $value, $form); parent::__construct($name, $title, $rows, $cols, $value, $form);
$this->extraClass = 'typography'; $this->extraClass = 'typography';
} }
@ -100,10 +100,10 @@ class HtmlEditorField extends TextareaField {
$content = preg_replace('/mce_real_src="[^"]+"/i', "", $content); $content = preg_replace('/mce_real_src="[^"]+"/i', "", $content);
$content = eregi_replace('(<img[^>]* )width=([0-9]+)( [^>]*>|>)','\\1width="\\2"\\3',$content); $content = eregi_replace('(<img[^>]* )width=([0-9]+)( [^>]*>|>)','\\1width="\\2"\\3', $content);
$content = eregi_replace('(<img[^>]* )height=([0-9]+)( [^>]*>|>)','\\1height="\\2"\\3',$content); $content = eregi_replace('(<img[^>]* )height=([0-9]+)( [^>]*>|>)','\\1height="\\2"\\3', $content);
$content = eregi_replace('src="([^\?]*)\?r=[0-9]+"','src="\\1"',$content); $content = eregi_replace('src="([^\?]*)\?r=[0-9]+"','src="\\1"', $content);
$content = eregi_replace('mce_src="([^\?]*)\?r=[0-9]+"','mce_src="\\1"',$content); $content = eregi_replace('mce_src="([^\?]*)\?r=[0-9]+"','mce_src="\\1"', $content);
$content = preg_replace_callback('/(<img[^>]* )(width="|height="|src=")([^"]+)("[^>]* )(width="|height="|src=")([^"]+)("[^>]* )(width="|height="|src=")([^"]+)("[^>]*>)/i', "HtmlEditorField_dataValue_processImage", $content); $content = preg_replace_callback('/(<img[^>]* )(width="|height="|src=")([^"]+)("[^>]* )(width="|height="|src=")([^"]+)("[^>]* )(width="|height="|src=")([^"]+)("[^>]*>)/i', "HtmlEditorField_dataValue_processImage", $content);
@ -111,11 +111,12 @@ class HtmlEditorField extends TextareaField {
if(!ereg("^[ \t\r\n]*<", $content)) $content = "<p>$content</p>"; if(!ereg("^[ \t\r\n]*<", $content)) $content = "<p>$content</p>";
$links = HTTP::getLinksIn($content); $links = HTTP::getLinksIn($content);
$linkedPages = array();
if($links) foreach($links as $link) { if($links) foreach($links as $link) {
$link = Director::makeRelative($link); $link = Director::makeRelative($link);
if(preg_match( '/^([A-Za-z0-9_-]+)\/?(#.*)?$/', $link, $parts ) ) { if(preg_match('/^([A-Za-z0-9_-]+)\/?(#.*)?$/', $link, $parts)) {
$candidatePage = DataObject::get_one("SiteTree", "\"URLSegment\" = '" . urldecode( $parts[1] ). "'", false); $candidatePage = DataObject::get_one("SiteTree", "\"URLSegment\" = '" . urldecode( $parts[1] ). "'", false);
if($candidatePage) { if($candidatePage) {
$linkedPages[] = $candidatePage->ID; $linkedPages[] = $candidatePage->ID;
@ -135,10 +136,8 @@ class HtmlEditorField extends TextareaField {
} }
$images = HTTP::getImagesIn($content); $images = HTTP::getImagesIn($content);
if($images) {
if($images){
foreach($images as $image) { foreach($images as $image) {
$image = Director::makeRelative($image); $image = Director::makeRelative($image);
if(substr($image,0,7) == 'assets/') { if(substr($image,0,7) == 'assets/') {
$candidateImage = DataObject::get_one("File", "\"Filename\" = '$image'"); $candidateImage = DataObject::get_one("File", "\"Filename\" = '$image'");
@ -150,7 +149,7 @@ class HtmlEditorField extends TextareaField {
$fieldName = $this->name; $fieldName = $this->name;
if($record->ID && $record->hasMethod('LinkTracking') && $linkTracking = $record->LinkTracking()) { if($record->ID && $record->hasMethod('LinkTracking') && $linkTracking = $record->LinkTracking()) {
$linkTracking->removeByFilter("\"FieldName\" = '$fieldName'"); $linkTracking->removeByFilter("\"FieldName\" = '$fieldName' AND \"SiteTreeID\" = $record->ID");
if(isset($linkedPages)) foreach($linkedPages as $item) { if(isset($linkedPages)) foreach($linkedPages as $item) {
$linkTracking->add($item, array("FieldName" => $fieldName)); $linkTracking->add($item, array("FieldName" => $fieldName));
@ -410,14 +409,14 @@ class HtmlEditorField_Toolbar extends RequestHandler {
new TreeDropdownField('FolderID', _t('HtmlEditorField.FOLDER', 'Folder'), 'Folder'), new TreeDropdownField('FolderID', _t('HtmlEditorField.FOLDER', 'Folder'), 'Folder'),
new LiteralField('AddFolderOrUpload', new LiteralField('AddFolderOrUpload',
'<div style="clear:both;"></div><div id="AddFolderGroup" style="display:inline"> '<div style="clear:both;"></div><div id="AddFolderGroup" style="display:inline">
<a style="" href="#" id="AddFolder" class="link">' . _t('HtmlEditorField.CREATEFOLDER','create folder') . '</a> <a style="" href="#" id="AddFolder" class="link">' . _t('HtmlEditorField.CREATEFOLDER','Create Folder') . '</a>
<input style="display: none; margin-left: 2px; width: 94px;" id="NewFolderName" class="addFolder" type="text"> <input style="display: none; margin-left: 2px; width: 94px;" id="NewFolderName" class="addFolder" type="text">
<a style="display: none;" href="#" id="FolderOk" class="link addFolder">' . _t('HtmlEditorField.OK','ok') . '</a> <a style="display: none;" href="#" id="FolderOk" class="link addFolder">' . _t('HtmlEditorField.OK','Ok') . '</a>
<a style="display: none;" href="#" id="FolderCancel" class="link addFolder">' . _t('HtmlEditorField.FOLDERCANCEL','cancel') . '</a> <a style="display: none;" href="#" id="FolderCancel" class="link addFolder">' . _t('HtmlEditorField.FOLDERCANCEL','Cancel') . '</a>
</div> </div>
<div id="PipeSeparator" style="display:inline">|</div> <div id="PipeSeparator" style="display:inline">|</div>
<div id="UploadGroup" class="group" style="display: inline; margin-top: 2px;"> <div id="UploadGroup" class="group" style="display: inline; margin-top: 2px;">
<a href="#" id="UploadFiles" class="link">' . _t('HtmlEditorField.UPLOAD','upload') . '</a> <a href="#" id="UploadFiles" class="link">' . _t('HtmlEditorField.UPLOAD','Upload') . '</a>
</div>' </div>'
), ),
new TextField('getimagesSearch', _t('HtmlEditorField.SEARCHFILENAME', 'Search by file name')), new TextField('getimagesSearch', _t('HtmlEditorField.SEARCHFILENAME', 'Search by file name')),

View File

@ -59,5 +59,4 @@ class ImageField extends FileField {
} }
} }
?> ?>

View File

@ -42,45 +42,15 @@ class ManyManyComplexTableField extends HasManyComplexTableField {
$this->joinField = 'Checked'; $this->joinField = 'Checked';
} }
function getQuery($limitClause = null) { function getQuery() {
if($this->customQuery) { $query = parent::getQuery();
$query = $this->customQuery; $query->select[] = "IF(`{$this->manyManyParentClass}ID` IS NULL, '0', '1') AS Checked";
$query->select[] = "{$this->sourceClass}.ID AS ID"; return $query;
$query->select[] = "{$this->sourceClass}.ClassName AS ClassName";
$query->select[] = "{$this->sourceClass}.ClassName AS \"RecordClassName\"";
}
else {
$query = singleton($this->sourceClass)->extendedSQL($this->sourceFilter, $this->sourceSort, $limitClause, $this->sourceJoin);
// Add more selected fields if they are from joined table.
$SNG = singleton($this->sourceClass);
foreach($this->FieldList() as $k => $title) {
if(! $SNG->hasField($k) && ! $SNG->hasMethod('get' . $k))
$query->select[] = $k;
}
$parent = $this->controllerClass();
$query->select[] = "IF(\"{$this->manyManyParentClass}ID\" IS NULL, '0', '1') AS Checked";
}
return clone $query;
} }
function getParentIdName($parentClass, $childClass) { function getParentIdName($parentClass, $childClass) {
return $this->getParentIdNameRelation($parentClass, $childClass, 'many_many'); return $this->getParentIdNameRelation($parentClass, $childClass, 'many_many');
} }
function ExtraData() {
$items = array();
foreach($this->unpagedSourceItems as $item) {
if($item->{$this->joinField})
$items[] = $item->ID;
}
$list = implode(',', $items);
$inputId = $this->id() . '_' . $this->htmlListEndName;
return <<<HTML
<input id="$inputId" name="{$this->name}[{$this->htmlListField}]" type="hidden" value="$list"/>
HTML;
}
} }
/** /**

View File

@ -6,6 +6,21 @@
*/ */
class PopupDateTimeField extends CalendarDateField { class PopupDateTimeField extends CalendarDateField {
/**
* @todo js validation needs to be implemented.
* @see sapphire/forms/DateField#jsValidation()
*/
function jsValidation() {
}
/**
* @todo php validation needs to be implemented.
* @see sapphire/forms/DateField#validate($validator)
*/
function validate() {
return true;
}
function Field() { function Field() {
Requirements::css( SAPPHIRE_DIR . '/css/PopupDateTimeField.css' ); Requirements::css( SAPPHIRE_DIR . '/css/PopupDateTimeField.css' );

View File

@ -393,5 +393,4 @@ class ReportField_Controller extends Controller {
} }
} }
?> ?>

View File

@ -8,11 +8,36 @@
class ResetFormAction extends FormAction { class ResetFormAction extends FormAction {
function Field() { function Field() {
$titleAttr = $this->description ? "title=\"" . Convert::raw2att($this->description) . "\"" : '';
if($this->useButtonTag) { if($this->useButtonTag) {
return "<button class=\"action " . $this->extraClass() . "\" id=\"" . $this->id() . "\" type=\"reset\" name=\"$this->action\" $titleAttr />" . $this->attrTitle() . "</button>\n"; $attributes = array(
'class' => 'action' . ($this->extraClass() ? $this->extraClass() : ''),
'id' => $this->id(),
'type' => 'reset',
'name' => $this->action
);
if($this->isReadonly()) {
$attributes['disabled'] = 'disabled';
$attributes['class'] = $attributes['class'] . ' disabled';
}
return $this->createTag('button', $attributes, $this->attrTitle());
} else { } else {
return "<input class=\"action " . $this->extraClass() . "\" id=\"" . $this->id() . "\" type=\"reset\" name=\"$this->action\" value=\"" . $this->attrTitle() . "\" $titleAttr />\n"; $attributes = array(
'class' => 'action' . ($this->extraClass() ? $this->extraClass() : ''),
'id' => $this->id(),
'type' => 'reset',
'name' => $this->action,
);
if($this->isReadonly()) {
$attributes['disabled'] = 'disabled';
$attributes['class'] = $attributes['class'] . ' disabled';
}
$attributes['title'] = ($this->description) ? $this->description : ($this->dontEscape) ? $this->Title() : $this->attrTitle();
return $this->createTag('input', $attributes);
} }
} }

View File

@ -56,7 +56,4 @@ class Tab extends CompositeField {
} }
} }
?> ?>

View File

@ -223,7 +223,7 @@ class TableField extends TableListField {
function SubmittedFieldSet(&$sourceItems){ function SubmittedFieldSet(&$sourceItems){
$fields = array (); $fields = array ();
if($rows = $_POST[$this->name]){ if(isset($_POST[$this->name])&&$rows = $_POST[$this->name]){
if(count($rows)){ if(count($rows)){
foreach($rows as $idx => $row){ foreach($rows as $idx => $row){
if($idx == 'new'){ if($idx == 'new'){

View File

@ -801,6 +801,7 @@ JS
} }
function FirstItem() { function FirstItem() {
if ($this->TotalCount() < 1) return 0;
return isset($_REQUEST['ctf'][$this->Name()]['start']) ? $_REQUEST['ctf'][$this->Name()]['start'] + 1 : 1; return isset($_REQUEST['ctf'][$this->Name()]['start']) ? $_REQUEST['ctf'][$this->Name()]['start'] + 1 : 1;
} }
@ -894,6 +895,8 @@ JS
/** /**
* Exports a given set of comma-separated IDs (from a previous search-query, stored in a HiddenField). * Exports a given set of comma-separated IDs (from a previous search-query, stored in a HiddenField).
* Uses {$csv_columns} if present, and falls back to {$result_columns}. * Uses {$csv_columns} if present, and falls back to {$result_columns}.
* We move the most filedata generation code to the function {@link generateExportFileData()} so that a child class
* could reuse the filedata generation code while overwrite export function.
* *
* @todo Make relation-syntax available (at the moment you'll have to use custom sql) * @todo Make relation-syntax available (at the moment you'll have to use custom sql)
*/ */
@ -901,19 +904,28 @@ JS
$now = Date("d-m-Y-H-i"); $now = Date("d-m-Y-H-i");
$fileName = "export-$now.csv"; $fileName = "export-$now.csv";
if($fileData = $this->generateExportFileData($numColumns, $numRows)){
return HTTPRequest::send_file($fileData, $fileName);
}else{
user_error("No records found", E_USER_ERROR);
}
}
function generateExportFileData(&$numColumns, &$numRows) {
$separator = $this->csvSeparator; $separator = $this->csvSeparator;
$csvColumns = ($this->fieldListCsv) ? $this->fieldListCsv : $this->fieldList; $csvColumns = ($this->fieldListCsv) ? $this->fieldListCsv : $this->fieldList;
$fileData = ""; $fileData = '';
$columnData = array();
$fieldItems = new DataObjectSet();
if($this->csvHasHeader) { if($this->csvHasHeader) {
$fileData .= "\"" . implode("\"{$separator}\"",array_values($csvColumns)) . "\""; $fileData .= "\"" . implode("\"{$separator}\"", array_values($csvColumns)) . "\"";
$fileData .= "\n"; $fileData .= "\n";
} }
// get data if(isset($this->customSourceItems)) {
if(isset($this->customSourceItems)){
$items = $this->customSourceItems; $items = $this->customSourceItems;
}else{ } else {
$dataQuery = $this->getCsvQuery(); $dataQuery = $this->getCsvQuery();
$records = $dataQuery->execute(); $records = $dataQuery->execute();
$sourceClass = $this->sourceClass; $sourceClass = $this->sourceClass;
@ -921,7 +933,6 @@ JS
$items = $dataobject->buildDataObjectSet($records, 'DataObjectSet'); $items = $dataobject->buildDataObjectSet($records, 'DataObjectSet');
} }
$fieldItems = new DataObjectSet();
if($items && $items->count()) foreach($items as $item) { if($items && $items->count()) foreach($items as $item) {
// create a TableListField_Item to support resolving of // create a TableListField_Item to support resolving of
// relation-fields in dot notation via TableListField_Item->Fields() // relation-fields in dot notation via TableListField_Item->Fields()
@ -934,15 +945,14 @@ JS
if($fieldItems) { if($fieldItems) {
foreach($fieldItems as $fieldItem) { foreach($fieldItems as $fieldItem) {
$columnData = array();
$fields = $fieldItem->Fields(); $fields = $fieldItem->Fields();
foreach($fields as $field) { $columnData = array();
if($fields) foreach($fields as $field) {
$value = $field->Value; $value = $field->Value;
// TODO This should be replaced with casting // TODO This should be replaced with casting
if(array_key_exists($field->Name, $this->csvFieldFormatting)) { if(array_key_exists($field->Name, $this->csvFieldFormatting)) {
$format = str_replace('$value', "__VAL__", $this->csvFieldFormatting[$columnName]); $format = str_replace('$value', "__VAL__", $this->csvFieldFormatting[$field->Name]);
$format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format); $format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
$format = str_replace('__VAL__', '$value', $format); $format = str_replace('__VAL__', '$value', $format);
eval('$value = "' . $format . '";'); eval('$value = "' . $format . '";');
@ -955,9 +965,12 @@ JS
$fileData .= implode($separator, $columnData); $fileData .= implode($separator, $columnData);
$fileData .= "\n"; $fileData .= "\n";
} }
return HTTPRequest::send_file($fileData, $fileName);
$numColumns = count($columnData);
$numRows = $fieldItems->count();
return $fileData;
} else { } else {
user_error("No records found", E_USER_ERROR); return null;
} }
} }

View File

@ -1,127 +0,0 @@
/**
* @author Tim Copeland
*/
// Browser detection
var BrowserDetect = {
init: function () {
this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
this.version = this.searchVersion(navigator.userAgent)
|| this.searchVersion(navigator.appVersion)
|| "an unknown version";
this.OS = this.searchString(this.dataOS) || "an unknown OS";
},
searchString: function (data) {
for (var i=0;i<data.length;i++) {
var dataString = data[i].string;
var dataProp = data[i].prop;
this.versionSearchString = data[i].versionSearch || data[i].identity;
if (dataString) {
if (dataString.indexOf(data[i].subString) != -1)
return data[i].identity;
}
else if (dataProp)
return data[i].identity;
}
},
searchVersion: function (dataString) {
var index = dataString.indexOf(this.versionSearchString);
if (index == -1) return;
return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
},
dataBrowser: [
{
string: navigator.vendor,
subString: "Apple",
identity: "Safari"
},
{
prop: window.opera,
identity: "Opera"
},
{
string: navigator.vendor,
subString: "iCab",
identity: "iCab"
},
{
string: navigator.vendor,
subString: "KDE",
identity: "Konqueror"
},
{
string: navigator.userAgent,
subString: "Firefox",
identity: "Firefox"
},
{ // for newer Netscapes (6+)
string: navigator.userAgent,
subString: "Netscape",
identity: "Netscape"
},
{
string: navigator.userAgent,
subString: "MSIE",
identity: "Explorer",
versionSearch: "MSIE"
},
{
string: navigator.userAgent,
subString: "Gecko",
identity: "Mozilla",
versionSearch: "rv"
},
{ // for older Netscapes (4-)
string: navigator.userAgent,
subString: "Mozilla",
identity: "Netscape",
versionSearch: "Mozilla"
}
],
dataOS : [
{
string: navigator.platform,
subString: "Win",
identity: "Windows"
},
{
string: navigator.platform,
subString: "Mac",
identity: "Mac"
},
{
string: navigator.platform,
subString: "Linux",
identity: "Linux"
}
]
};
BrowserDetect.init();
Behaviour.register({
'#all' : {
initialise: function() {
if( BrowserDetect.browser == "Firefox" /*&& BrowserDetect.version >= 1.5*/ ||
(BrowserDetect.browser == "Explorer" && BrowserDetect.version >= 6) ||
(BrowserDetect.browser == "Mozilla" && BrowserDetect.version >= 1.2)
) {
$("noSupport").style.display = "none"
$("supported").style.display = "block"
}
//center the all holder
$("all").style.left = (document.body.offsetWidth /2) - ( $("all").offsetWidth /2) -2 + "px"
}
},
'#LoginForm_LoginForm_action_login' : {
onclick: function() {
document.body.style.backgroundColor = "white";
// Effect.Puff("all");
return true;
}
}
})

View File

@ -136,8 +136,10 @@ TableListField.prototype = {
{ {
postBody: 'update=1', postBody: 'update=1',
onComplete: function(response) { onComplete: function(response) {
Element.replace(this.id, response.responseText) Element.replace(this.id, response.responseText);
Behaviour.apply($(this.id)) // reapply behaviour and reattach methods to TF container node
// e.g. <div class="TableListField">
Behaviour.apply($(this.id), true);
}.bind(this) }.bind(this)
} }
); );

View File

@ -134,7 +134,7 @@ TreeDropdownField.prototype = {
this.treeShown = false; this.treeShown = false;
if(this.itemTree) { if(this.itemTree) {
this.itemTree.style.display = 'none'; this.itemTree.style.display = 'none';
Event.stopObserving(document, 'click', this.bound_testForBlur); if(this.bound_testForBlur) Event.stopObserving(document, 'click', this.bound_testForBlur);
// this.editLink.style.display = this.humanItems.style.display = 'block'; // this.editLink.style.display = this.humanItems.style.display = 'block';
this.unstretchIframeIfNeeded(); this.unstretchIframeIfNeeded();
} }

View File

@ -57,7 +57,7 @@ if($majorVersion < 5) {
*/ */
require_once("core/Core.php"); require_once("core/Core.php");
header("Content-type: text/html; charset=\"utf-8\""); if(!headers_sent()) header("Content-type: text/html; charset=\"utf-8\"");
if (function_exists('mb_http_output')) { if (function_exists('mb_http_output')) {
mb_http_output('UTF-8'); mb_http_output('UTF-8');
mb_internal_encoding('UTF-8'); mb_internal_encoding('UTF-8');
@ -86,8 +86,7 @@ if (isset($_GET['url'])) {
} }
// Fix glitches in URL generation // Fix glitches in URL generation
if (substr($url, 0, strlen(BASE_URL)) == BASE_URL) $url = substr($url, strlen(BASE_URL)); if (substr(strtolower($url), 0, strlen(BASE_URL)) == strtolower(BASE_URL)) $url = substr($url, strlen(BASE_URL));
if (isset($_GET['debug_profile'])) { if (isset($_GET['debug_profile'])) {
Profiler::init(); Profiler::init();

View File

@ -213,17 +213,20 @@ class SSHTMLBBCodeParser
$filter = ucfirst($filter); $filter = ucfirst($filter);
if (!array_key_exists($filter, $this->_filters)) { if (!array_key_exists($filter, $this->_filters)) {
$class = 'SSHTMLBBCodeParser_Filter_'.$filter; $class = 'SSHTMLBBCodeParser_Filter_'.$filter;
@include_once 'BBCodeParser/Filter/'.$filter.'.php'; if (fopen('BBCodeParser/Filter/'.$filter.'.php','r',true)) {
include_once 'BBCodeParser/Filter/'.$filter.'.php';
}
if (!class_exists($class)) { if (!class_exists($class)) {
//PEAR::raiseError("Failed to load filter $filter", null, PEAR_ERROR_DIE); //PEAR::raiseError("Failed to load filter $filter", null, PEAR_ERROR_DIE);
} }
else {
$this->_filters[$filter] = new $class; $this->_filters[$filter] = new $class;
$this->_definedTags = array_merge( $this->_definedTags = array_merge(
$this->_definedTags, $this->_definedTags,
$this->_filters[$filter]->_definedTags $this->_filters[$filter]->_definedTags
); );
}
} }
} }

9
sake
View File

@ -62,10 +62,17 @@ if [ "$1" = "-start" ]; then
sake=`realpath $0` sake=`realpath $0`
base=`realpath $base` base=`realpath $base`
# if third argument is not explicitly given, copy from second argument
if [ "$3" = "" ]; then
url=$2
else
url=$3
fi
# TODO: Give a globally unique processname by including the projectname as well # TODO: Give a globally unique processname by including the projectname as well
processname=$2 processname=$2
daemon -n $processname -r -D $base --pidfile=$pidfile --stdout=$outlog --stderr=$errlog $sake $2 $3 daemon -n $processname -r -D $base --pidfile=$pidfile --stdout=$outlog --stderr=$errlog $sake $url
else else
echo "Service $2 seems to already be running" echo "Service $2 seems to already be running"
fi fi

View File

@ -134,5 +134,4 @@ class AdvancedSearchForm extends SearchForm {
} }
?> ?>

View File

@ -31,6 +31,11 @@ class SearchForm extends Form {
*/ */
protected $pageLength = 10; protected $pageLength = 10;
/**
* Classes to search
*/
protected $classesToSearch = array("SiteTree", "File");
/** /**
* *
* @param Controller $controller * @param Controller $controller
@ -73,6 +78,19 @@ class SearchForm extends Form {
)); ));
} }
/**
* Set the classes to search.
* Currently you can only choose from "SiteTree" and "File", but a future version might improve this.
*/
function classesToSearch($classes) {
$illegalClasses = array_diff($classes, array('SiteTree', 'File'));
if($illegalClasses) {
user_error("SearchForm::classesToSearch() passed illegal classes '" . implode("', '", $illegalClasses) . "'. At this stage, only File and SiteTree are allowed", E_USER_WARNING);
}
$legalClasses = array_intersect($classes, array('SiteTree', 'File'));
$this->classesToSearch = $legalClasses;
}
/** /**
* Return dataObjectSet of the results using $_REQUEST to get info from form. * Return dataObjectSet of the results using $_REQUEST to get info from form.
* Wraps around {@link searchEngine()}. * Wraps around {@link searchEngine()}.
@ -150,66 +168,71 @@ class SearchForm extends Form {
public function searchEngine($keywords, $pageLength = null, $sortBy = "Relevance DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false) { public function searchEngine($keywords, $pageLength = null, $sortBy = "Relevance DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false) {
if(!$pageLength) $pageLength = $this->pageLength; if(!$pageLength) $pageLength = $this->pageLength;
$fileFilter = ''; $fileFilter = '';
$keywords = Convert::raw2sql($keywords); $keywords = Convert::raw2sql($keywords);
$htmlEntityKeywords = htmlentities($keywords); $htmlEntityKeywords = htmlentities($keywords);
$extraFilters = array('SiteTree' => '', 'File' => '');
if($booleanSearch) $boolean = "IN BOOLEAN MODE"; if($booleanSearch) $boolean = "IN BOOLEAN MODE";
if($extraFilter) { if($extraFilter) {
$extraFilter = " AND $extraFilter"; $extraFilters['SiteTree'] = " AND $extraFilter";
$fileFilter = ($alternativeFileFilter) ? " AND $alternativeFileFilter" : $extraFilter;
if($alternativeFileFilter) $extraFilters['File'] = " AND $alternativeFileFilter";
else $extraFilters['File'] = $extraFilters['SiteTree'];
} }
if($this->showInSearchTurnOn) $extraFilter .= " AND showInSearch <> 0"; if($this->showInSearchTurnOn) $extraFilters['SiteTree'] .= " AND showInSearch <> 0";
$start = isset($_GET['start']) ? (int)$_GET['start'] : 0; $start = isset($_GET['start']) ? (int)$_GET['start'] : 0;
$limit = $start . ", " . (int) $pageLength; $limit = $start . ", " . (int) $pageLength;
$notMatch = $invertedMatch ? "NOT " : ""; $notMatch = $invertedMatch ? "NOT " : "";
if($keywords) { if($keywords) {
$matchContent = " $match['SiteTree'] = "MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$keywords' $boolean)";
MATCH (Title, MenuTitle, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$keywords' $boolean) $match['File'] = "MATCH (Filename, Title, Content) AGAINST ('$keywords' $boolean) AND ClassName = 'File'";
+ MATCH (Content) AGAINST ('$htmlEntityKeywords' $boolean)
";
$matchFile = "MATCH (Filename, Title, Content) AGAINST ('$keywords' $boolean) AND ClassName = 'File'";
// We make the relevance search by converting a boolean mode search into a normal one // We make the relevance search by converting a boolean mode search into a normal one
$relevanceKeywords = str_replace(array('*','+','-'),'',$keywords); $relevanceKeywords = str_replace(array('*','+','-'),'',$keywords);
$htmlEntityRelevanceKeywords = str_replace(array('*','+','-'),'',$htmlEntityKeywords); $htmlEntityRelevanceKeywords = str_replace(array('*','+','-'),'',$htmlEntityKeywords);
$relevanceContent = " $relevance['SiteTree'] = "MATCH (Title) AGAINST ('$relevanceKeywords') + MATCH(Content) AGAINST ('$htmlEntityRelevanceKeywords') + MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$relevanceKeywords')";
MATCH (Title) AGAINST ('$relevanceKeywords') $relevance['File'] = "MATCH (Filename, Title, Content) AGAINST ('$relevanceKeywords')";
+ MATCH(Content) AGAINST ('$htmlEntityRelevanceKeywords')
+ MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$relevanceKeywords')
";
$relevanceFile = "MATCH (Filename, Title, Content) AGAINST ('$relevanceKeywords')";
} else { } else {
$relevanceContent = $relevanceFile = 1; $relevance['SiteTree'] = $relevance['File'] = 1;
$matchContent = $matchFile = "1 = 1"; $match['SiteTree'] = $match['File'] = "1 = 1";
} }
$queryContent = singleton('SiteTree')->extendedSQL($notMatch . $matchContent . $extraFilter, ""); // Generate initial queries and base table names
$baseClasses = array('SiteTree' => '', 'File' => '');
foreach($this->classesToSearch as $class) {
$queries[$class] = singleton($class)->extendedSQL($notMatch . $match[$class] . $extraFilters[$class], "");
$baseClasses[$class] = reset($queries[$class]->from);
}
$baseClass = reset($queryContent->from); // Make column selection lists
// There's no need to do all that joining $select = array(
$queryContent->from = array(str_replace(array('`','"'),'',$baseClass) => $baseClass); 'SiteTree' => array("ClassName","$baseClasses[SiteTree].ID","ParentID","Title","URLSegment","Content","LastEdited","Created","_utf8'' AS Filename", "_utf8'' AS Name", "$relevance[SiteTree] AS Relevance", "CanViewType"),
$queryContent->select = array("\"ClassName\"","$baseClass.\"ID\"","\"ParentID\"","\"Title\"", 'File' => array("ClassName","$baseClasses[File].ID","_utf8'' AS ParentID","Title","_utf8'' AS URLSegment","Content","LastEdited","Created","Filename","Name","$relevance[File] AS Relevance","NULL AS CanViewType"),
"\"URLSegment\"","\"Content\"","\"LastEdited\"","\"Created\"","'' AS \"Filename\"", );
"'' AS \"Name\"", "$relevanceContent AS \"Relevance\"", "\"CanViewType\"");
$queryContent->orderby = null;
$queryFiles = singleton('File')->extendedSQL($notMatch . $matchFile . $fileFilter, ""); // Process queries
$baseClass = reset($queryFiles->from); foreach($this->classesToSearch as $class) {
// There's no need to do all that joining // There's no need to do all that joining
$queryFiles->from = array(str_replace(array('`','"'),'',$baseClass) => $baseClass); $queries[$class]->from = array(str_replace('`','',$baseClasses[$class]) => $baseClasses[$class]);
$queryFiles->select = array("\"ClassName\"","$baseClass.\"ID\"","'' AS \"ParentID\"","\"Title\"", $queries[$class]->select = $select[$class];
"'' AS \"URLSegment\"","\"Content\"","\"LastEdited\"","\"Created\"","\"Filename\"","\"Name\"", $queries[$class]->orderby = null;
"$relevanceFile AS \"Relevance\"","NULL AS \"CanViewType\""); }
$queryFiles->orderby = null;
$fullQuery = $queryContent->sql() . " UNION " . $queryFiles->sql() . " ORDER BY $sortBy LIMIT $limit"; // Combine queries
$totalCount = $queryContent->unlimitedRowCount() + $queryFiles->unlimitedRowCount(); $querySQLs = array();
$totalCount = 0;
foreach($queries as $query) {
$querySQLs[] = $query->sql();
$totalCount += $query->unlimitedRowCount();
}
$fullQuery = implode(" UNION ", $querySQLs) . " ORDER BY $sortBy LIMIT $limit";
// Get records
$records = DB::query($fullQuery); $records = DB::query($fullQuery);
foreach($records as $record) foreach($records as $record)

View File

@ -186,5 +186,4 @@ abstract class Authenticator extends Object {
} }
} }
?> ?>

View File

@ -100,5 +100,4 @@ class ChangePasswordForm extends Form {
} }
?> ?>

View File

@ -279,6 +279,14 @@ class Group extends DataObject {
} }
} }
public function canView() {
if($this->hasMethod('alternateCanView')) return $this->alternateCanView();
else {
return Permission::check("ADMIN")
|| (Member::currentUserID() && !DataObject::get("Permission", "GroupID = $this->ID AND Code = 'ADMIN'"));
}
}
/** /**
* Returns all of the children for the CMS Tree. * Returns all of the children for the CMS Tree.
* Filters to only those groups that the current user can edit * Filters to only those groups that the current user can edit

View File

@ -27,6 +27,12 @@ class LoginAttempt extends DataObject {
'Member' => 'Member', // only linked if the member actually exists 'Member' => 'Member', // only linked if the member actually exists
); );
static $has_many = array();
static $many_many = array();
static $belongs_many_many = array();
function fieldLabels() { function fieldLabels() {
$labels = parent::fieldLabels(); $labels = parent::fieldLabels();
$labels['Email'] = _t('LoginAttempt.Email', 'Email Address'); $labels['Email'] = _t('LoginAttempt.Email', 'Email Address');

View File

@ -7,17 +7,16 @@
class Member extends DataObject { class Member extends DataObject {
static $db = array( static $db = array(
'FirstName' => "Varchar", 'FirstName' => 'Varchar',
'Surname' => "Varchar", 'Surname' => 'Varchar',
'Email' => "Varchar", 'Email' => 'Varchar',
'Password' => "Varchar(64)", // support for up to SHA256! 'Password' => 'Varchar(64)', // support for up to SHA256!
'RememberLoginToken' => "Varchar(50)", 'RememberLoginToken' => 'Varchar(50)',
'NumVisit' => "Int", 'NumVisit' => 'Int',
'LastVisited' => 'SSDatetime', 'LastVisited' => 'SSDatetime',
'Bounced' => 'Boolean', // Note: This does not seem to be used anywhere. 'Bounced' => 'Boolean', // Note: This does not seem to be used anywhere.
'AutoLoginHash' => 'Varchar(30)', 'AutoLoginHash' => 'Varchar(30)',
'AutoLoginExpired' => 'SSDatetime', 'AutoLoginExpired' => 'SSDatetime',
'BlacklistedEmail' => 'Boolean',
'PasswordEncryption' => "Enum('none', 'none')", 'PasswordEncryption' => "Enum('none', 'none')",
'Salt' => 'Varchar(50)', 'Salt' => 'Varchar(50)',
'PasswordExpiry' => 'Date', 'PasswordExpiry' => 'Date',
@ -26,11 +25,13 @@ class Member extends DataObject {
); );
static $belongs_many_many = array( static $belongs_many_many = array(
"Groups" => "Group", 'Groups' => 'Group',
); );
static $has_one = array(); static $has_one = array();
static $has_many = array(); static $has_many = array();
static $many_many = array(); static $many_many = array();
static $many_many_extraFields = array(); static $many_many_extraFields = array();
@ -68,6 +69,15 @@ class Member extends DataObject {
'Email', 'Email',
); );
/**
* The unique field used to identify this member.
* By default, it's "Email", but another common
* field could be Username.
*
* @var string
*/
protected static $unique_identifier_field = 'Email';
/** /**
* {@link PasswordValidator} object for validating user's password * {@link PasswordValidator} object for validating user's password
*/ */
@ -130,8 +140,30 @@ class Member extends DataObject {
// This can be called via CLI during testing. // This can be called via CLI during testing.
if(Director::is_cli()) return; if(Director::is_cli()) return;
$file = ""; $line = ""; $file = '';
if (!headers_sent($file, $line)) session_regenerate_id(true); $line = '';
if(!headers_sent($file, $line)) session_regenerate_id(true);
}
/**
* Get the field used for uniquely identifying a member
* in the database. {@see Member::$unique_identifier_field}
*
* @return string
*/
static function get_unique_identifier_field() {
return self::$unique_identifier_field;
}
/**
* Set the field used for uniquely identifying a member
* in the database. {@see Member::$unique_identifier_field}
*
* @param $field The field name to set as the unique field
*/
static function set_unique_identifier_field($field) {
self::$unique_identifier_field = $field;
} }
/** /**
@ -198,7 +230,10 @@ class Member extends DataObject {
Session::set('Member.FailedLogins', $failedLogins); Session::set('Member.FailedLogins', $failedLogins);
} }
$this->LockedOutUntil = null; // Don't set column if its not built yet (the login might be precursor to a /dev/build...)
if(array_key_exists('LockedOutUntil', DB::fieldList('Member'))) {
$this->LockedOutUntil = null;
}
$this->write(); $this->write();
@ -388,30 +423,6 @@ class Member extends DataObject {
} }
/**
* Add the members email address to the blacklist
*
* With this method the blacklisted email table is updated to ensure that
* no promotional material is sent to the member (newsletters).
* Standard system messages are still sent such as receipts.
*
* @param bool $val Set to TRUE if the address should be added to the
* blacklist, otherwise to FALSE.
*/
function setBlacklistedEmail($val) {
if($val && $this->Email) {
$blacklisting = new Email_BlackList();
$blacklisting->BlockedEmail = $this->Email;
$blacklisting->MemberID = $this->ID;
$blacklisting->write();
}
$this->setField("BlacklistedEmail", $val);
// Save the BlacklistedEmail field to the Member table
$this->write();
}
/* /*
* Generate a random password, with randomiser to kick in if there's no words file on the * Generate a random password, with randomiser to kick in if there's no words file on the
* filesystem. * filesystem.
@ -437,29 +448,18 @@ class Member extends DataObject {
} }
} }
/** /**
* Event handler called before writing to the database * Event handler called before writing to the database.
*
* If an email's filled out look for a record with the same email and if
* found update this record to merge with that member.
*/ */
function onBeforeWrite() { function onBeforeWrite() {
if($this->SetPassword) $this->Password = $this->SetPassword; if($this->SetPassword) $this->Password = $this->SetPassword;
if($this->Email) { $identifierField = self::$unique_identifier_field;
if($this->ID) { if($this->$identifierField) {
$idClause = "AND \"Member\".\"ID\" <> $this->ID"; $idClause = ($this->ID) ? " AND \"Member\".\"ID\" <> $this->ID" : '';
} else { $SQL_identifierField = Convert::raw2sql($this->$identifierField);
$idClause = "";
}
$existingRecord = DataObject::get_one(
"Member", "\"Email\" = '" . addslashes($this->Email) . "' $idClause");
// Debug::message("Found an existing member for email $this->Email");
$existingRecord = DataObject::get_one('Member', "\"$identifierField\" = '{$SQL_identifierField}'{$idClause}");
if($existingRecord) { if($existingRecord) {
$newID = $existingRecord->ID; $newID = $existingRecord->ID;
if($this->ID) { if($this->ID) {
@ -761,15 +761,13 @@ class Member extends DataObject {
$groupIDList = array(); $groupIDList = array();
if(is_a($groups, 'DataObjectSet')) { if(is_a($groups, 'DataObjectSet')) {
foreach($groups as $group) foreach($groups as $group) {
$groupIDList[] = $group->ID; $groupIDList[] = $group->ID;
}
} elseif(is_array($groups)) { } elseif(is_array($groups)) {
$groupIDList = $groups; $groupIDList = $groups;
} }
/*if( empty( $groupIDList ) )
return Member::map(); */
$filterClause = ($groupIDList) $filterClause = ($groupIDList)
? "\"GroupID\" IN (" . implode( ',', $groupIDList ) . ")" ? "\"GroupID\" IN (" . implode( ',', $groupIDList ) . ")"
: ""; : "";
@ -792,8 +790,7 @@ class Member extends DataObject {
* @return array Groups in which the member is NOT in. * @return array Groups in which the member is NOT in.
*/ */
public function memberNotInGroups($groupList, $memberGroups = null){ public function memberNotInGroups($groupList, $memberGroups = null){
if(!$memberGroups) if(!$memberGroups) $memberGroups = $this->Groups();
$memberGroups = $this->Groups();
foreach($memberGroups as $group) { foreach($memberGroups as $group) {
if(in_array($group->Code, $groupList)) { if(in_array($group->Code, $groupList)) {
@ -801,6 +798,7 @@ class Member extends DataObject {
unset($groupList[$index]); unset($groupList[$index]);
} }
} }
return $groupList; return $groupList;
} }
@ -846,11 +844,6 @@ class Member extends DataObject {
$locale $locale
)); ));
$mainFields->insertAfter(
new TreeMultiselectField("Groups", _t("Member.SECURITYGROUPS", "Security groups")),
'Locale'
);
$mainFields->removeByName('Bounced'); $mainFields->removeByName('Bounced');
$mainFields->removeByName('RememberLoginToken'); $mainFields->removeByName('RememberLoginToken');
$mainFields->removeByName('AutoLoginHash'); $mainFields->removeByName('AutoLoginHash');
@ -861,14 +854,14 @@ class Member extends DataObject {
$mainFields->removeByName('Salt'); $mainFields->removeByName('Salt');
$mainFields->removeByName('NumVisit'); $mainFields->removeByName('NumVisit');
$mainFields->removeByName('LastVisited'); $mainFields->removeByName('LastVisited');
$mainFields->removeByName('BlacklistedEmail');
$fields->removeByName('Subscriptions'); $fields->removeByName('Subscriptions');
$fields->removeByName('UnsubscribedRecords');
// Groups relation will get us into logical conflicts because // Groups relation will get us into logical conflicts because
// Members are displayed within group edit form in SecurityAdmin // Members are displayed within group edit form in SecurityAdmin
$fields->removeByName('Groups'); $fields->removeByName('Groups');
$this->extend('updateCMSFields', $fields);
return $fields; return $fields;
} }
@ -935,10 +928,6 @@ class Member extends DataObject {
if($valid->valid()) { if($valid->valid()) {
$this->AutoLoginHash = null; $this->AutoLoginHash = null;
$this->write(); $this->write();
// Emails will be sent by Member::onBeforeWrite().
//$this->sendinfo('changePassword', array('CleartextPassword' => $password));
} }
return $valid; return $valid;
@ -1372,26 +1361,35 @@ class Member_Validator extends RequiredFields {
function php($data) { function php($data) {
$valid = parent::php($data); $valid = parent::php($data);
$member = DataObject::get_one('Member', $identifierField = Member::get_unique_identifier_field();
"\"Email\" = '". Convert::raw2sql($data['Email']) ."'");
// if we are in a complex table field popup, use ctf[childID], else use $SQL_identifierField = Convert::raw2sql($data[$identifierField]);
// ID $member = DataObject::get_one('Member', "\"$identifierField\" = '{$SQL_identifierField}'");
if(isset($_REQUEST['ctf']['childID']))
// if we are in a complex table field popup, use ctf[childID], else use ID
if(isset($_REQUEST['ctf']['childID'])) {
$id = $_REQUEST['ctf']['childID']; $id = $_REQUEST['ctf']['childID'];
elseif(isset($_REQUEST['ID'])) } elseif(isset($_REQUEST['ID'])) {
$id = $_REQUEST['ID']; $id = $_REQUEST['ID'];
else } else {
$id = null; $id = null;
if($id && is_object($member) && $member->ID != $id) {
$emailField = $this->form->dataFieldByName('Email');
$this->validationError($emailField->id(),
_t('Member.VALIDATIONMEMBEREXISTS', "There already exists a member with this email"),
"required");
$valid = false;
} }
if($id && is_object($member) && $member->ID != $id) {
$uniqueField = $this->form->dataFieldByName($identifierField);
$this->validationError(
$uniqueField->id(),
sprintf(
_t(
'Member.VALIDATIONMEMBEREXISTS',
'A member already exists with the same %s'
),
strtolower($identifierField)
),
'required'
);
$valid = false;
}
// Execute the validators on the extensions // Execute the validators on the extensions
if($this->extension_instances) { if($this->extension_instances) {
@ -1402,7 +1400,6 @@ class Member_Validator extends RequiredFields {
} }
} }
return $valid; return $valid;
} }
@ -1429,9 +1426,7 @@ class Member_Validator extends RequiredFields {
return $js; return $js;
} }
} }
// Initialize the static DB variables to add the supported encryption // Initialize the static DB variables to add the supported encryption
// algorithms to the PasswordEncryption Enum field // algorithms to the PasswordEncryption Enum field
Member::init_db_fields(); Member::init_db_fields();
?> ?>

View File

@ -114,5 +114,4 @@ class MemberAuthenticator extends Authenticator {
} }
} }
?> ?>

View File

@ -37,6 +37,15 @@ class MemberLoginForm extends LoginForm {
Requirements::css($customCSS); Requirements::css($customCSS);
} }
// Focus on the email input when the page is loaded
Requirements::customScript("
(function($){
$(document).ready(function() {
$('#Email input').focus();
});
})(jQuery);
");
if(isset($_REQUEST['BackURL'])) { if(isset($_REQUEST['BackURL'])) {
$backURL = $_REQUEST['BackURL']; $backURL = $_REQUEST['BackURL'];
} else { } else {
@ -50,8 +59,8 @@ class MemberLoginForm extends LoginForm {
if(!$fields) { if(!$fields) {
$fields = new FieldSet( $fields = new FieldSet(
new HiddenField("AuthenticationMethod", null, $this->authenticator_class, $this), new HiddenField("AuthenticationMethod", null, $this->authenticator_class, $this),
new TextField("Email", _t('Member.EMAIL'), Session::get('SessionForms.MemberLoginForm.Email'), null, $this), new TextField("Email", _t('Member.EMAIL', 'Email'), Session::get('SessionForms.MemberLoginForm.Email'), null, $this),
new PasswordField("Password", _t('Member.PASSWORD'), null, $this) new PasswordField("Password", _t('Member.PASSWORD', 'Password'))
); );
if(Security::$autologin_enabled) { if(Security::$autologin_enabled) {
$fields->push(new CheckboxField( $fields->push(new CheckboxField(
@ -231,6 +240,4 @@ class MemberLoginForm extends LoginForm {
} }
} }
?> ?>

View File

@ -12,9 +12,15 @@ class MemberPassword extends DataObject {
); );
static $has_one = array( static $has_one = array(
'Member' => 'Member', 'Member' => 'Member'
); );
static $has_many = array();
static $many_many = array();
static $belongs_many_many = array();
/** /**
* Log a password change from the given member. * Log a password change from the given member.
* Call MemberPassword::log($this) from within Member whenever the password is changed. * Call MemberPassword::log($this) from within Member whenever the password is changed.

View File

@ -22,6 +22,11 @@ class Permission extends DataObject {
static $defaults = array( static $defaults = array(
"Type" => 1 "Type" => 1
); );
static $has_many = array();
static $many_many = array();
static $belongs_many_many = array();
/** /**
* This is the value to use for the "Type" field if a permission should be * This is the value to use for the "Type" field if a permission should be
@ -106,6 +111,16 @@ class Permission extends DataObject {
} }
private static $cache_permissions = array();
/**
* Flush the permission cache, for example if you have edited group membership or a permission record.
* @todo Call this whenever Group_Members is added to or removed from
*/
public static function flush_permission_cache() {
self::$cache_permissions = array();
}
/** /**
* Check that the given member has the given permission * Check that the given member has the given permission
* @param int|Member memberID The ID of the member to check. Leave blank for the current member. * @param int|Member memberID The ID of the member to check. Leave blank for the current member.
@ -124,6 +139,12 @@ class Permission extends DataObject {
$perms_list = self::get_declared_permissions_list(); $perms_list = self::get_declared_permissions_list();
$memberID = (is_object($member)) ? $member->ID : $member; $memberID = (is_object($member)) ? $member->ID : $member;
// Simple cache. This could be improved a lot by actually downloading all of the given user's permissions in one hit
$codeStr = is_array($code) ? implode(',',$code) : $code;
if($arg == 'any' && isset(self::$cache_permissions[$memberID][$codeStr])) {
return self::$cache_permissions[$memberID][$codeStr];
}
/* /*
if(self::$declared_permissions && is_array($perms_list) && !in_array($code, $perms_list)) { if(self::$declared_permissions && is_array($perms_list) && !in_array($code, $perms_list)) {
user_error( user_error(
@ -134,6 +155,8 @@ class Permission extends DataObject {
} }
*/ */
$groupList = self::groupList($memberID); $groupList = self::groupList($memberID);
if(!$groupList) return false; if(!$groupList) return false;
@ -177,8 +200,10 @@ class Permission extends DataObject {
) )
")->value(); ")->value();
if($permission) if($permission) {
self::$cache_permissions[$memberID][$codeStr] = $permission;
return $permission; return $permission;
}
// Strict checking disabled? // Strict checking disabled?
if(!self::$strict_checking || !$strict) { if(!self::$strict_checking || !$strict) {
@ -190,11 +215,14 @@ class Permission extends DataObject {
AND (\"Type\" = " . self::GRANT_PERMISSION . ") AND (\"Type\" = " . self::GRANT_PERMISSION . ")
) )
")->value(); ")->value();
if(!$hasPermission) { if(!$hasPermission) {
self::$cache_permissions[$memberID][$codeStr] = true;
return true; return true;
} }
} }
self::$cache_permissions[$memberID][$codeStr] = false;
return false; return false;
} }
@ -544,6 +572,13 @@ class Permission extends DataObject {
} }
} }
} }
public function onBeforeWrite() {
parent::onBeforeWrite();
// Just in case we've altered someone's permissions
Permission::flush_permission_cache();
}
} }
@ -612,5 +647,4 @@ class Permission_Group {
} }
} }
?> ?>

View File

@ -174,20 +174,12 @@ class Security extends Controller {
// Work out the right message to show // Work out the right message to show
if(Member::currentUserID()) { if(Member::currentUserID()) {
// user_error( 'PermFailure with member', E_USER_ERROR ); $message = isset($messageSet['alreadyLoggedIn']) ? $messageSet['alreadyLoggedIn'] : $messageSet['default'];
if($member = Member::currentUser()) {
$message = isset($messageSet['alreadyLoggedIn']) $member->logOut();
? $messageSet['alreadyLoggedIn'] }
: $messageSet['default'];
if($member = Member::currentUser())
$member->logout();
} else if(substr(Director::history(),0,15) == 'Security/logout') { } else if(substr(Director::history(),0,15) == 'Security/logout') {
$message = $messageSet['logInAgain'] $message = $messageSet['logInAgain'] ? $messageSet['logInAgain'] : $messageSet['default'];
? $messageSet['logInAgain']
: $messageSet['default'];
} else { } else {
$message = $messageSet['default']; $message = $messageSet['default'];
} }
@ -356,11 +348,7 @@ class Security extends Controller {
Session::clear('Security.Message'); Session::clear('Security.Message');
// custom processing // custom processing
if(SSViewer::hasTemplate("Security_login")) { return $customisedController->renderWith(array('Security_login', 'Security', $this->stat('template_main')));
return $customisedController->renderWith(array("Security_login", $this->stat('template_main')));
} else {
return $customisedController->renderWith($this->stat('template_main'));
}
} }
function basicauthlogin() { function basicauthlogin() {
@ -398,7 +386,7 @@ class Security extends Controller {
)); ));
//Controller::$currentController = $controller; //Controller::$currentController = $controller;
return $customisedController->renderWith($this->stat('template_main')); return $customisedController->renderWith(array('Security_lostpassword', 'Security', $this->stat('template_main')));
} }
@ -412,7 +400,7 @@ class Security extends Controller {
$this, $this,
'LostPasswordForm', 'LostPasswordForm',
new FieldSet( new FieldSet(
new EmailField('Email', _t('Member.EMAIL')) new EmailField('Email', _t('Member.EMAIL', 'Email'))
), ),
new FieldSet( new FieldSet(
new FormAction( new FormAction(
@ -456,7 +444,7 @@ class Security extends Controller {
)); ));
//Controller::$currentController = $controller; //Controller::$currentController = $controller;
return $customisedController->renderWith($this->stat('template_main')); return $customisedController->renderWith(array('Security_passwordsent', 'Security', $this->stat('template_main')));
} }
@ -525,7 +513,7 @@ class Security extends Controller {
} }
//Controller::$currentController = $controller; //Controller::$currentController = $controller;
return $customisedController->renderWith($this->stat('template_main')); return $customisedController->renderWith(array('Security_changepassword', 'Security', $this->stat('template_main')));
} }
/** /**
@ -982,5 +970,4 @@ class Security extends Controller {
} }
?> ?>

View File

@ -64,7 +64,7 @@
<% end_control %> <% end_control %>
<% control Actions %> <% control Actions %>
<td width="16" class="action<% if Default %> default<% end_if %>"><a class="$Class" href="$Link"><% if Icon %><img src="$Icon" alt="$Label" /><% else %>$Label<% end_if %></a></td> <td width="16" class="action<% if Default %> default<% end_if %>"><a class="$Class" href="$Link"><% if Icon %><img src="$Icon" alt="$Label" /><% else %>$Label<% end_if %></a></td>
<% end_if %> <% end_control %>
</tr> </tr>
<% end_control %> <% end_control %>
<% else %> <% else %>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<% control Items %>
<url>
<loc>$AbsoluteLink</loc>
<lastmod>$LastEdited.Format(c)</lastmod>
<% if ChangeFreq %><changefreq>$ChangeFreq</changefreq><% end_if %>
<% if Priority %><priority>$Priority</priority><% end_if %>
</url>
<% end_control %>
</urlset>

View File

@ -1,4 +1,4 @@
<div id="$id" class="$CSSClasses"> <div id="$id" class="$CSSClasses" href="$CurrentLink">
<% include TableListField_PageControls %> <% include TableListField_PageControls %>
<table class="data"> <table class="data">
<thead> <thead>

33
tests/ClassInfoTest.php Normal file
View File

@ -0,0 +1,33 @@
<?php
/**
* @package sapphire
* @subpackage tests
*/
class ClassInfoTest extends SapphireTest {
function testSubclassesFor() {
$this->assertEquals(
ClassInfo::subclassesFor('ClassInfoTest_BaseClass'),
array(
0 => 'ClassInfoTest_BaseClass',
'ClassInfoTest_ChildClass' => 'ClassInfoTest_ChildClass',
'ClassInfoTest_GrandChildClass' => 'ClassInfoTest_GrandChildClass'
),
'ClassInfo::subclassesFor() returns only direct subclasses and doesnt include base class'
);
}
}
class ClassInfoTest_BaseClass {
}
class ClassInfoTest_ChildClass extends ClassInfoTest_BaseClass {
}
class ClassInfoTest_GrandChildClass extends ClassInfoTest_ChildClass {
}
?>

View File

@ -7,6 +7,21 @@
*/ */
class HTTPTest extends SapphireTest { class HTTPTest extends SapphireTest {
/**
* Tests {@link HTTP::getLinksIn()}
*/
public function testGetLinksIn() {
$content = '
<h2>My page</h2>
<p>A boy went <a href="home/">home</a> to see his <span><a href="mother/">mother</a></span>.</p>
';
$links = HTTP::getLinksIn($content);
$this->assertTrue(is_array($links));
$this->assertTrue(count($links) == 2);
}
/** /**
* Tests {@link HTTP::setGetVar()} * Tests {@link HTTP::setGetVar()}
*/ */

View File

@ -57,6 +57,7 @@ class RestfulServiceTest extends SapphireTest {
class RestfulServiceTest_Controller extends Controller { class RestfulServiceTest_Controller extends Controller {
public function index() { public function index() {
ContentNegotiator::disable(); ContentNegotiator::disable();
BasicAuth::disable();
$request_count = count($_REQUEST); $request_count = count($_REQUEST);
$get_count = count($_GET); $get_count = count($_GET);
$post_count = count($_POST); $post_count = count($_POST);

View File

@ -23,10 +23,11 @@ class CsvBulkLoaderTest extends SapphireTest {
$this->assertEquals(4, $results->Count(), 'Test correct count of imported data'); $this->assertEquals(4, $results->Count(), 'Test correct count of imported data');
// Test that columns were correctly imported // Test that columns were correctly imported
$obj = Dataobject::get_one("CsvBulkLoaderTest_Player", "\"FirstName\" = 'John'"); $obj = DataObject::get_one("CsvBulkLoaderTest_Player", "\"FirstName\" = 'John'");
$this->assertNotNull($obj); $this->assertNotNull($obj);
$this->assertEquals("He's a good guy", $obj->Biography); $this->assertEquals("He's a good guy", $obj->Biography);
$this->assertEquals("1988-01-31", $obj->Birthday); $this->assertEquals("1988-01-31", $obj->Birthday);
$this->assertEquals("1", $obj->IsRegistered);
fclose($file); fclose($file);
} }
@ -44,7 +45,8 @@ class CsvBulkLoaderTest extends SapphireTest {
'FirstName', 'FirstName',
'Biography', 'Biography',
null, // ignored column null, // ignored column
'Birthday' 'Birthday',
'IsRegistered'
); );
$loader->hasHeaderRow = false; $loader->hasHeaderRow = false;
$results = $loader->load($filepath); $results = $loader->load($filepath);
@ -53,10 +55,15 @@ class CsvBulkLoaderTest extends SapphireTest {
$this->assertEquals(4, $results->Count(), 'Test correct count of imported data'); $this->assertEquals(4, $results->Count(), 'Test correct count of imported data');
// Test that columns were correctly imported // Test that columns were correctly imported
$obj = Dataobject::get_one("CsvBulkLoaderTest_Player", "\"FirstName\" = 'John'"); $obj = DataObject::get_one("CsvBulkLoaderTest_Player", "\"FirstName\" = 'John'");
$this->assertNotNull($obj); $this->assertNotNull($obj);
$this->assertEquals("He's a good guy", $obj->Biography); $this->assertEquals("He's a good guy", $obj->Biography);
$this->assertEquals("1988-01-31", $obj->Birthday); $this->assertEquals("1988-01-31", $obj->Birthday);
$this->assertEquals("1", $obj->IsRegistered);
$obj2 = DataObject::get_one('CsvBulkLoaderTest_Player', "FirstName = 'Jane'");
$this->assertNotNull($obj2);
$this->assertEquals('0', $obj2->IsRegistered);
fclose($file); fclose($file);
} }
@ -133,7 +140,6 @@ class CsvBulkLoaderTest extends SapphireTest {
$player = DataObject::get_by_id('CsvBulkLoaderTest_Player', 1); $player = DataObject::get_by_id('CsvBulkLoaderTest_Player', 1);
$this->assertEquals($player->FirstName, 'JohnUpdated', 'Test updating of existing records works'); $this->assertEquals($player->FirstName, 'JohnUpdated', 'Test updating of existing records works');
$this->assertEquals($player->Biography, 'He\'s a good guy', 'Test retaining of previous information on duplicate when overwriting with blank field'); $this->assertEquals($player->Biography, 'He\'s a good guy', 'Test retaining of previous information on duplicate when overwriting with blank field');
} }
function testLoadWithCustomImportMethods() { function testLoadWithCustomImportMethods() {
@ -142,12 +148,14 @@ class CsvBulkLoaderTest extends SapphireTest {
$loader->columnMap = array( $loader->columnMap = array(
'FirstName' => '->importFirstName', 'FirstName' => '->importFirstName',
'Biography' => 'Biography', 'Biography' => 'Biography',
'Birthday' => 'Birthday' 'Birthday' => 'Birthday',
'IsRegistered' => 'IsRegistered'
); );
$results = $loader->load($filepath); $results = $loader->load($filepath);
$player = DataObject::get_by_id('CsvBulkLoaderTest_Player', 1); $player = DataObject::get_by_id('CsvBulkLoaderTest_Player', 1);
$this->assertEquals($player->FirstName, 'Customized John'); $this->assertEquals($player->FirstName, 'Customized John');
$this->assertEquals($player->Biography, "He's a good guy"); $this->assertEquals($player->Biography, "He's a good guy");
$this->assertEquals($player->IsRegistered, "1");
} }
protected function getLineCount(&$file) { protected function getLineCount(&$file) {
@ -185,6 +193,7 @@ class CsvBulkLoaderTest_Player extends DataObject implements TestOnly {
'Biography' => 'HTMLText', 'Biography' => 'HTMLText',
'Birthday' => 'Date', 'Birthday' => 'Date',
'ExternalIdentifier' => 'Varchar(255)', // used for uniqueness checks on passed property 'ExternalIdentifier' => 'Varchar(255)', // used for uniqueness checks on passed property
'IsRegistered' => 'Boolean'
); );
static $has_one = array( static $has_one = array(

View File

@ -1,4 +1 @@
"John","He's a good guy","ignored","31/01/1988" John,He's a good guy,ignored,31/01/88,1 Jane,"She is awesome.\nSo awesome that she gets multiple rows and \escaped\"" strings in her biography""",ignored,31/01/82,0 Jamie,"Pretty old\, with an escaped comma",ignored,31/01/1882,1 Järg,Unicode FTW,ignored,31/06/1982,1
"Jane","She is awesome.\nSo awesome that she gets multiple rows and \"escaped\" strings in her biography","ignored","31/01/1982"
"Jamie","Pretty old\, with an escaped comma","ignored","31/01/1882"
"Järg","Unicode FTW","ignored","31/06/1982"
Can't render this file because it contains an unexpected character in line 2 and column 70.

View File

@ -1,6 +1,2 @@
"FirstName","Biography","Birthday" FirstName,Biography,Birthday,IsRegistered John,He's a good guy,31/01/88,1 Jane,"She is awesome.
"John","He's a good guy","31/01/1988" So awesome that she gets multiple rows and \escaped\"" strings in her biography""",31/01/82,0 Jamie,"Pretty old\, with an escaped comma",31/01/1882,1 Järg,Unicode FTW,31/06/1982,1
"Jane","She is awesome.
So awesome that she gets multiple rows and \"escaped\" strings in her biography","31/01/1982"
"Jamie","Pretty old\, with an escaped comma","31/01/1882"
"Järg","Unicode FTW","31/06/1982"
Can't render this file because it contains an unexpected character in line 4 and column 45.

View File

@ -0,0 +1,44 @@
<?php
/**
* @package sapphire
* @subpackage tests
*/
class DateTest extends SapphireTest {
function testNiceDate() {
/* Test the DD/MM/YYYY formatting of Date::Nice() */
$cases = array(
'4/3/03' => '04/03/2003',
'04/03/03' => '04/03/2003',
'4/3/03' => '04/03/2003',
'4/03/03' => '04/03/2003',
'4/3/2003' => '04/03/2003',
'4-3-2003' => '04/03/2003',
'2003-03-04' => '04/03/2003',
'04/03/2003' => '04/03/2003',
'04-03-2003' => '04/03/2003'
);
foreach($cases as $original => $expected) {
$date = new Date();
$date->setValue($original);
$this->assertEquals($expected, $date->Nice());
}
}
function testLongDate() {
/* "24 May 2006" style formatting of Date::Long() */
$cases = array(
'2003-4-3' => '3 April 2003',
'3/4/2003' => '3 April 2003',
);
foreach($cases as $original => $expected) {
$date = new Date();
$date->setValue($original);
$this->assertEquals($expected, $date->Long());
}
}
}
?>

View File

@ -0,0 +1,39 @@
<?php
/**
* @package sapphire
* @subpackage tests
*/
class DateFieldTest extends SapphireTest {
function testDMYFormat() {
/* We get YYYY-MM-DD format as the data value for DD/MM/YYYY input value */
$dateField = new DateField('Date', 'Date', '04/03/2003');
$this->assertEquals($dateField->dataValue(), '2003-03-04');
/* Even if value hasn't got leading 0's in it we still get the correct data value */
$dateField2 = new DateField('Date', 'Date', '4/3/03');
$this->assertEquals($dateField2->dataValue(), '03-3-4');
}
function testYMDFormat() {
/* We get YYYY-MM-DD format as the data value for YYYY-MM-DD input value */
$dateField = new DateField('Date', 'Date', '2003/03/04');
$this->assertEquals($dateField->dataValue(), '2003-03-04');
/* Even if input value hasn't got leading 0's in it we still get the correct data value */
$dateField2 = new DateField('Date', 'Date', '2003/3/4');
$this->assertEquals($dateField2->dataValue(), '2003-03-04');
}
function testMDYFormat() {
/* We get MM-DD-YYYY format as the data value for YYYY-MM-DD input value */
$dateField = new DateField('Date', 'Date', '03/04/2003');
$this->assertEquals($dateField->dataValue(), '2003-04-03');
/* Even if input value hasn't got leading 0's in it we still get the correct data value */
$dateField2 = new DateField('Date', 'Date', '3/4/03');
$this->assertEquals($dateField2->dataValue(), '03-4-3');
}
}
?>

View File

@ -14,6 +14,11 @@ class Widget extends DataObject {
"Parent" => "WidgetArea", "Parent" => "WidgetArea",
); );
static $has_many = array();
static $many_many = array();
static $belongs_many_many = array();
static $defaults = array();
static $default_sort = "Sort"; static $default_sort = "Sort";
static $title = "Widget Title"; static $title = "Widget Title";

View File

@ -5,12 +5,19 @@
* @subpackage widgets * @subpackage widgets
*/ */
class WidgetArea extends DataObject { class WidgetArea extends DataObject {
static $db = array(); static $db = array();
static $has_one = array();
static $has_many = array( static $has_many = array(
"Widgets" => "Widget" "Widgets" => "Widget"
); );
static $many_many = array();
static $belongs_many_many = array();
function forTemplate() { function forTemplate() {
return $this->renderWith($this->class); return $this->renderWith($this->class);
} }