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' => '*',
'images' => 'Image_Uploader',
'' => 'RootURLController',
'sitemap.xml' => 'GoogleSitemap',
'api/v1' => 'RestfulServer',
'soap/v1' => 'SOAPModelAccess',
'dev' => 'DevelopmentAdmin',
@ -47,7 +46,7 @@ Object::useCustomClass('Datetime','SSDatetime',true);
* Add pear parser to include path
*/
$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
@ -70,7 +69,4 @@ define('MCE_ROOT', 'jsparty/tiny_mce2/');
*/
define('EMAIL_BOUNCEHANDLER_KEY', '1aaaf8fb60ea253dbf6efa71baaacbb3');
?>

View File

@ -193,6 +193,7 @@ class RSSFeed extends ViewableData {
* Return the content of the RSS feed
*/
function feedContent() {
SSViewer::set_source_file_comments(false);
return str_replace(' ', ' ', $this->renderWith('RSSFeed'));
}
}
@ -301,4 +302,4 @@ class RSSFeed_Entry extends ViewableData {
else user_error($this->failover->class . " object has either an AbsoluteLink nor a Link method. Can't put a link in the RSS feed", E_USER_WARNING);
}
}
?>
?>

View File

@ -137,6 +137,11 @@ class RestfulServer extends Controller {
$id = (isset($this->urlParams['ID'])) ? $this->urlParams['ID'] : 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
$apiAccess = singleton($className)->stat('api_access');
if(!$apiAccess) return $this->permissionFailure();

View File

@ -124,8 +124,15 @@ class RestfulService extends ViewableData {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$responseBody = curl_exec($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);
return;
}
@ -338,4 +345,4 @@ class RestfulService_Response extends HTTPResponse {
}
}
?>
?>

View File

@ -29,7 +29,7 @@ class XMLDataFormatter extends DataFormatter {
* @return String XML
*/
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);
}
@ -111,7 +111,7 @@ class XMLDataFormatter extends DataFormatter {
* @return String XML
*/
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;
$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";

View File

@ -11,8 +11,10 @@
*/
abstract class CliController extends Controller {
function init() {
$this->disableBasicAuth();
parent::init();
$this->disableBasicAuth();
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() {
@ -29,5 +31,6 @@ abstract class CliController extends Controller {
* Overload this method to contain the task logic.
*/
function process() {}
}
?>
}
?>

View File

@ -93,16 +93,36 @@ class ClassInfo {
/**
* 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
* @return array
* @return array Names of all subclasses as an associative array.
*/
static function subclassesFor($class){
global $_ALL_CLASSES;
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;
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;
}

View File

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

View File

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

View File

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

View File

@ -515,10 +515,6 @@ class Object {
public static function remove_extension($className, $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.
@ -606,8 +602,6 @@ class Object {
* @param string|int $id An id for the cache
* @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) {
if(!$this->class) {
$this->class = get_class($this);
@ -632,4 +626,4 @@ class Object {
return $data;
}
}
?>
?>

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()}
*
* @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) {
self::backend()->custom($script, $uniquenessID);
self::backend()->customCSS($script, $uniquenessID);
}
/**
* Add the following custom code to the <head> section of the page.
* 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
* in the head section
*
* @see {@link Requirements_Backend::set_write_js_to_body()}
* @see Requirements_Backend::set_write_js_to_body()
* @param boolean
*/
static function set_write_js_to_body($var) {
@ -390,7 +391,12 @@ class Requirements_Backend {
$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) {
if($uniquenessID)
$this->customCSS[$uniquenessID] = $script;
@ -399,7 +405,6 @@ class Requirements_Backend {
}
}
/**
* Add the following custom code to the <head> section of the page.
*
@ -444,7 +449,7 @@ class Requirements_Backend {
}
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";
}
}
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";
}
@ -775,6 +780,7 @@ class Requirements_Backend {
*
*/
function process_combined_files() {
if(Director::isDev() && !SapphireTest::is_running_test()) {
return;
}
@ -803,7 +809,7 @@ class Requirements_Backend {
$newJSRequirements[$file] = true;
}
}
foreach($this->css as $file => $params) {
if(isset($combinerCheck[$file])) {
$newCSSRequirements[$combinerCheck[$file]] = true;
@ -812,7 +818,7 @@ class Requirements_Backend {
$newCSSRequirements[$file] = $params;
}
}
// Process the combined files
$base = Director::baseFolder() . '/';
foreach(array_diff_key($combinedFiles,$this->blocked) as $combinedFile => $dummy) {
@ -857,20 +863,11 @@ class Requirements_Backend {
fclose($fh);
unset($fh);
}
// Unsuccessful write - just include the regular JS files, rather than the combined one
if(!$successfulWrite) {
user_error("Requirements_Backend::process_combined_files(): Couldn't create '$base$combinedFile'", E_USER_WARNING);
$keyedFileList = array();
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)
);
}
return;
}
}
@ -925,5 +922,4 @@ class Requirements_Backend {
}
?>

View File

@ -48,16 +48,29 @@
* @subpackage view
*/
class SSViewer extends Object {
/**
* @var boolean $source_file_comments
*/
protected static $source_file_comments = true;
/**
* 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
*
* @param boolean $val
*/
function set_source_file_comments($val) {
static function set_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
* template containers: "main" and "Layout".
@ -355,6 +368,7 @@ class SSViewer extends Object {
static function parseTemplateContent($content, $template="") {
// Add template filename comments on dev sites
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(stripos($content, "<html") !== false) {
@ -369,9 +383,9 @@ class SSViewer extends Object {
$oldContent = $content;
// 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])
. "\n<!-- end include " . SSViewer::getTemplateFile($matches[1]) . "-->";';
. "\n<!-- end include " . SSViewer::getTemplateFile($matches[1]) . " -->";';
else $replacementCode = 'return SSViewer::getTemplateContent($matches[1]);';
$content = preg_replace_callback('/<' . '% include +([A-Za-z0-9_]+) +%' . '>/', create_function(
@ -578,4 +592,4 @@ function supressOutput() {
return "";
}
?>
?>

View File

@ -236,4 +236,4 @@ class Session {
return self::$timeout;
}
}
?>
?>

View File

@ -18,7 +18,7 @@ class ViewableData extends Object implements IteratorAggregate {
* @var int
*/
protected $iteratorPos;
/**
* Total number of items in the iterator.
* @var int
@ -1221,4 +1221,4 @@ class ViewableData_Iterator implements Iterator {
}
}
?>
?>

View File

@ -224,7 +224,7 @@ JS
if($this->dataRecord){
$thisPage = $this->dataRecord->Link();
$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 {
/**
* 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->setVal($date);
$archiveLink = "<a class=\"current\">Archived Site</a>";
$liveLink = "<a href=\"$thisPage?stage=Live\" target=\"site\" style=\"left : -3px;\">Published Site</a>";
$stageLink = "<a href=\"$thisPage?stage=Stage\" target=\"site\" style=\"left : -1px;\">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>";
$archiveLink = "<a class=\"current\">". _t('ContentController.ARCHIVEDSITE', 'Archived 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;\">". _t('ContentController.DRAFTSITE', 'Draft Site') ."</a>";
$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') {
$stageLink = "<a class=\"current\">Draft Site</a>";
$liveLink = "<a href=\"$thisPage?stage=Live\" target=\"site\" style=\"left : -3px;\">Published Site</a>";
$message = "<div id=\"SilverStripeNavigatorMessage\" title=\"Note: this message won't be shown to your visitors\">DRAFT SITE</div>";
$stageLink = "<a class=\"current\">". _t('ContentController.DRAFTSITE', 'Draft Site') ."</a>";
$liveLink = "<a href=\"$thisPage?stage=Live\" target=\"site\" style=\"left : -3px;\">". _t('ContentController.PUBLISHEDSITE', 'Published Site') ."</a>";
$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 {
$liveLink = "<a class=\"current\">Published Site</a>";
$stageLink = "<a href=\"$thisPage?stage=Stage\" target=\"site\" style=\"left : -1px;\">Draft Site</a>";
$message = "<div id=\"SilverStripeNavigatorMessage\" title=\"Note: this message won't be shown to your visitors\">PUBLISHED SITE</div>";
$liveLink = "<a class=\"current\">". _t('ContentController.PUBLISHEDSITE', 'Published Site') ."</a>";
$stageLink = "<a href=\"$thisPage?stage=Stage\" target=\"site\" style=\"left : -1px;\">". _t('ContentController.DRAFTSITE', 'Draft Site') ."</a>";
$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) {
$firstname = Convert::raw2xml($member->FirstName);
$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 {
$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
* CMS if there is no dataRecord
@ -277,7 +277,7 @@ JS
</div>
<div id="switchView" class="bottomTabs">
<div class="blank"> View page in: </div>
<div class="blank">$viewPageIn </div>
$cmsLink
$stageLink
<div class="blank" style="width:1em;"> </div>
@ -295,7 +295,7 @@ HTML;
Requirements::css(SAPPHIRE_DIR . '/css/SilverStripeNavigator.css');
$dateObj = Object::create('Datetime', $date, null);
// $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
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");
$content = str_replace('&nbsp;','&#160;', $content);
@ -134,7 +134,7 @@ class ContentNegotiator {
* Removes "xmlns" attributes and any <?xml> Pragmas.
*/
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");
$content = $response->getBody();
@ -174,10 +174,15 @@ class ContentNegotiator {
* By default, negotiation is only enabled for pages that have the xml header.
*/
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;
else return (substr($response->getBody(),0,5) == '<' . '?xml');
}
}
?>
?>

View File

@ -544,4 +544,4 @@ class Controller extends RequestHandler {
}
}
?>
?>

View File

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

View File

@ -102,4 +102,4 @@ class ModelAsController extends Controller implements NestedController {
}
}
?>
?>

View File

@ -22,7 +22,7 @@ class RootURLController extends Controller {
$controller = new ModelAsController();
$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);

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)
* 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
*/
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;
$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);
} else {
}
// $name is assumed to be a PHP class
else {
global $_CLASS_MANIFEST;
if(strpos($name,'_') !== false) $name = strtok($name,'_');
if(isset($_CLASS_MANIFEST[$name])) {
@ -1076,6 +1087,7 @@ class i18n extends Object {
$module = self::get_owner_module($class);
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'))) {
include($file);
@ -1088,6 +1100,12 @@ class i18n extends Object {
} else if(file_exists(Director::getAbsFile("$module/lang"))) {
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()}.
*
* 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
* entity name as the key, and a numerical array of pseudo-arguments
* for _t() as a value.

View File

@ -110,6 +110,16 @@ class i18nTextCollector extends Object {
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
@ -228,7 +238,7 @@ class i18nTextCollector extends Object {
if(class_exists($class) && in_array('i18nEntityProvider', class_implements($class))) {
$reflectionClass = new ReflectionClass($class);
if($reflectionClass->isAbstract()) continue;
$obj = singleton($class);
$entitiesArr = array_merge($entitiesArr,(array)$obj->provideI18nEntities());
}

View File

@ -262,4 +262,4 @@ class DB {
return DB::$globalConn->quiet();
}
}
?>
?>

View File

@ -1581,7 +1581,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
* Used for simple frontend forms without relation editing
* or {@link TabSet} behaviour. Uses {@link scaffoldFormFields()}
* 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
*
@ -1590,7 +1590,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
*/
public function getFrontEndFields($params = null) {
$untabbedFields = $this->scaffoldFormFields($params);
$this->extend('updateFormFields', $untabbedFields);
$this->extend('updateFrontEndFields', $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.
*/
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(get_parent_class($dataClass) == 'DataObject') {
self::$cache_has_own_table[$dataClass] = true;
@ -2736,7 +2739,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
$fields = array();
// try to scaffold a couple of usual suspects
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('FirstName')) $fields['FirstName'] = 'First Name';
}
@ -2848,7 +2851,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
/**
* Inserts standard column-values when a DataObject
* 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 disable the default value given by a parent class, set the default value to 0,'',or false in your
@ -3001,4 +3004,4 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
}
?>
?>

View File

@ -57,7 +57,6 @@ abstract class DataObjectDecorator extends Extension {
return $this->loadExtraStatics();
}
/**
* Edit the given query object to support queries for this extension
*
@ -66,7 +65,6 @@ abstract class DataObjectDecorator extends Extension {
function augmentSQL(SQLQuery &$query) {
}
/**
* 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
* 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
* formfields to the record. The method {@link updateCMSFields()}
* 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
* by the decorator.
* This function is used to provide modifications to the form used
* for front end forms. {@link DataObject->getFrontEndFields()}
*
* Caution: Use {@link FieldSet->push()} to add fields.
*
* @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) {
}
@ -176,6 +181,12 @@ abstract class DataObjectDecorator extends Extension {
$extra_fields = $this->extraStatics();
if(isset($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);
}
}
@ -201,5 +212,4 @@ abstract class DataObjectDecorator extends Extension {
}
}
?>
?>

View File

@ -264,6 +264,85 @@ class DataObjectSet extends ViewableData implements IteratorAggregate {
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.
* @return boolean
@ -694,7 +773,7 @@ class DataObjectSet extends ViewableData implements IteratorAggregate {
$myViewer = SSViewer::fromString($currentTemplate);
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
@ -1044,4 +1123,4 @@ class DataObjectSet_Iterator implements Iterator {
}
}
?>
?>

View File

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

View File

@ -103,6 +103,7 @@ class ErrorPage extends Page {
$errorContent = $response->getBody();
// Check we have an assets base directory, creating if it we don't
if(!file_exists(ASSETS_PATH)) {
mkdir(ASSETS_PATH, 02775);
}
@ -113,6 +114,17 @@ class ErrorPage extends Page {
if($fh = fopen($filePath, "w")) {
fwrite($fh, $errorContent);
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.

View File

@ -159,7 +159,7 @@ class Hierarchy extends DataObjectDecorator {
protected function markingFinished() {
// Mark childless nodes as expanded.
foreach($this->markedNodes as $id => $node) {
if(!$node->numChildren()) {
if(!$node->isExpanded() && !$node->numChildren()) {
$node->markExpanded();
}
}
@ -351,7 +351,18 @@ class Hierarchy extends DataObjectDecorator {
* @return DataObjectSet
*/
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

@ -780,4 +780,4 @@ class Image_Uploader extends Controller {
}
}
?>
?>

View File

@ -174,11 +174,12 @@ class MySQLDatabase extends Database {
public function createTable($tableName, $fields = null, $indexes = null) {
$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($indexes) foreach($indexes as $k => $v) $fieldSchemas .= $this->getIndexSqlDefinition($k, $v) . ",\n";
$this->query("CREATE TABLE \"$tableName\" (
ID int(11) not null auto_increment,
$fieldSchemas
$indexSchemas
primary key (ID)
@ -468,7 +469,7 @@ class MySQLDatabase extends Database {
//$parts=Array('datatype'=>'decimal', 'precision'=>"$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

@ -452,4 +452,4 @@ class SQLQuery extends Object {
}
}
?>
?>

View File

@ -191,6 +191,42 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
*/
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.
*
@ -525,7 +561,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @todo Check we get a endless recursion if we use parent::can()
*/
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;
@ -562,7 +598,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return boolean True if the current user can add children.
*/
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;
// 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.
*/
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
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.
*/
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;
@ -690,7 +726,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return boolean True if the current user can create pages on this class.
*/
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;
@ -726,7 +762,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return boolean True if the current user can edit this page.
*/
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;
@ -774,7 +810,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return boolean True if the current user can publish this page.
*/
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;
@ -816,7 +852,6 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
/**
* 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
*
* @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->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();
$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(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
// Create a status message for multiple parents
if($this->ID && is_numeric($this->ID)) {
@ -1139,18 +1156,37 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$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
$fields = new FieldSet(
new TabSet("Root",
$tabContent = new TabSet('Content',
$tabMain = new Tab('Main',
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 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", "")
),
$tabReports = new TabSet('Reports',
$tabBacklinks =new Tab('Backlinks',
new LiteralField("Backlinks", $backlinks)
$tabBacklinks = new Tab('Backlinks',
$backLinksNote,
$backLinksTable
)
),
$tabAccess = new Tab('Access',
@ -1496,11 +1533,10 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return array
*/
protected function getClassDropdown() {
$classes = ClassInfo::getValidSubClasses('SiteTree');
array_shift($classes);
$classes = self::page_type_classes();
$currentClass = null;
$result = array();
$result = array();
foreach($classes as $class) {
$instance = singleton($class);
@ -1812,4 +1848,4 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
}
}
?>
?>

View File

@ -617,6 +617,27 @@ class Versioned extends DataObjectDecorator {
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.
*
@ -802,4 +823,4 @@ class Versioned_Version extends ViewableData {
}
}
?>
?>

View File

@ -170,4 +170,4 @@ class VirtualPage_Controller extends Page_Controller {
}
}
?>
?>

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() {
return $this->name;

View File

@ -42,6 +42,10 @@ class Decimal extends DBField {
public function scaffoldFormField($title = null, $params = null) {
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.

View File

@ -60,4 +60,4 @@ class Int extends DBField {
}
?>
?>

View File

@ -6,6 +6,10 @@
*/
class SSDatetime extends Date {
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));
else $value = null;
}
@ -41,4 +45,4 @@ class SSDatetime extends Date {
}
}
?>
?>

View File

@ -321,4 +321,4 @@ class Text extends DBField {
}
}
?>
?>

View File

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

View File

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

View File

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

View File

@ -137,4 +137,24 @@ form .message {
color:#FF4040;
width:240px;
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 {
margin-left: 0;
}

View File

@ -4,24 +4,25 @@
bottom: 0;
left: 0;
width: 100%;
border-top: 3px solid #d4d0c8;
border-top: 2px solid #d4d0c8;
background-color:#81858d;
height: 18px;
height: 22px;
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 {
text-align: center;
padding-top : 3px;
padding-top : 4px;
padding-left : 3px;
padding-right : 6px;
font-size: 10px;
color: white;
border-top: 1px solid #555555;
}
#SilverStripeNavigator #logInStatus {
float: right;
@ -32,72 +33,52 @@
}
#SilverStripeNavigator a {
color: #333;
background-color: transparent;
text-decoration: none;
}
#SilverStripeNavigator a:hover {
color: #333;
color: #fff;
background-color: transparent;
text-decoration: underline;
}
#SilverStripeNavigator a:hover {
background-color: transparent;
}
#SilverStripeNavigator .bottomTabs a {
width: auto;
display: block;
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;
margin-right: 8px;
text-decoration: underline;
}
#SilverStripeNavigator .bottomTabs div.blank {
display: block;
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;
top : -2px;
cursor: pointer;
border : none;
background-color: transparent;
padding-right: 2px;
padding-left: 2px;
padding-top : 2px;
color:#FFFFFF;
padding: 2px 4px 2px 2px;
font-weight: bold;
}
#SilverStripeNavigator .bottomTabs a.current {
background-color : #d4d0c8;
padding-top : 1px;
top : -5px;
height : 15px;
font-weight:bold;
font-size : 11px;
border : 1px solid #555555;
text-decoration: none;
}
#SilverStripeNavigatorMessage {
font-family: 'Lucida Grande', Verdana, Arial, 'sans-serif';
position: absolute;
right: 45%;
top: 10px;
right: 20px;
top: 40px;
padding: 10px;
border-color: #c99;
color: #fff;
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%;
}
/* HACK Preventing IE6 from showing double borders */
/* Preventing IE6 from showing double borders */
body>div table.TableField,
body>div table.TableListField,
body>div .TableListField table.data,
@ -113,6 +113,7 @@ table.CMSList tbody td.checkbox {
table.TableField 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{
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
* 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
* 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".
*
* <code>
@ -222,7 +229,7 @@ abstract class BulkLoader extends ViewableData {
* @return boolean
*/
protected function isNullValue($val, $fieldName = null) {
return (empty($val));
return (empty($val) && $val !== '0');
}
}

View File

@ -130,4 +130,4 @@ class DevelopmentAdmin extends Controller {
}
}
?>
?>

View File

@ -11,4 +11,4 @@ class InstallerTest extends Controller {
}
}
?>
?>

View File

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

View File

@ -76,4 +76,4 @@ class SSCli extends Object {
}
}
?>
?>

View File

@ -236,4 +236,4 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
}
}
?>
?>

View File

@ -262,6 +262,22 @@ class Email extends ViewableData {
$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() {
if($this->template_data) {
return $this->template_data->customise(array(
@ -384,6 +400,8 @@ class Email extends ViewableData {
if(trim($headers['Bcc'])) $headers['Bcc'] .= ', ';
$headers['Bcc'] .= self::$bcc_all_emails_to;
}
Requirements::restore();
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 $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])
* - 'hex': Hexadecimal URL-Encoding - useful for mailto: links
* @return string
*/
public static function obfuscate($email, $method = 'visible') {
switch($method) {
case 'direction' :
Requirements::customCSS(
'span.codedirection { unicode-bidi: bidi-override; direction: rtl; }',
'codedirectionCSS'
);
return '<span class="codedirection">' . strrev($email) . '</span>';
case 'visible' :
$obfuscated = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
return strtr($email, $obfuscated);
@ -714,38 +739,21 @@ class Email_BounceRecord extends DataObject {
static $has_one = array(
'Member' => 'Member'
);
static $has_many = array();
static $many_many = array();
static $defaults = array();
/**
* a record of Email_BounceRecord can't be created manually. Instead, it should be
* created though system.
*/
public function canCreate($member = null) {
return false;
}
}
/**
* 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'
);
/**
* Helper function to see if the email being
* sent has specifically been blocked.
*/
static function isBlocked($email){
$blockedEmails = DataObject::get("Email_BlackList")->toDropDownMap("ID","BlockedEmail");
if($blockedEmails){
if(in_array($email,$blockedEmails)){
return true;
}else{
return false;
}
}else{
return false;
}
}
}
?>
?>

View File

@ -17,7 +17,7 @@ class Mailer extends Object {
* Send a plain-text email
*/
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;
return $mimeData;
}

View File

@ -17,6 +17,12 @@ class QueuedEmail extends DataObject {
'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
function canSendEmail() {
return true;
@ -27,4 +33,4 @@ class QueuedEmail extends DataObject {
$email->send();
}
}
?>
?>

View File

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

View File

@ -124,4 +124,4 @@ class Filesystem extends Object {
}
?>
?>

View File

@ -441,11 +441,11 @@ class Folder extends File {
*/
function getUploadIframe() {
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>
HTML;
}
}
?>
?>

View File

@ -408,4 +408,4 @@ class GD extends Object {
}
?>
?>

View File

@ -4,6 +4,15 @@
*
* ASSUMPTION -> IF you pass your source as an array, you pass values as an array too.
* 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
* @subpackage fields-basic
*/
@ -11,11 +20,12 @@ class CheckboxSetField extends OptionsetField {
protected $disabled = false;
/**
* Object handles arrays and dosets being passed by reference.
*
* @todo Should use CheckboxField FieldHolder rather than constructing own markup.
*/
/**
* @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.
*/
function Field() {
Requirements::css(SAPPHIRE_DIR . '/css/CheckboxSetField.css');
@ -86,10 +96,10 @@ class CheckboxSetField extends OptionsetField {
$checked = '';
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";
}
@ -108,7 +118,7 @@ class CheckboxSetField extends OptionsetField {
if(!$value && $obj && $obj instanceof DataObject && $obj->hasMethod($this->name)) {
$funcName = $this->name;
$selected = $obj->$funcName();
$value = $selected->toDropdownMap('ID','ID');
$value = $selected->toDropdownMap('ID', 'ID');
}
parent::setValue($value, $obj);
@ -133,7 +143,7 @@ class CheckboxSetField extends OptionsetField {
$record->$fieldname()->setByIDList($idList);
} elseif($fieldname && $record) {
if($this->value) {
$this->value = str_replace(",", "{comma}", $this->value);
$this->value = str_replace(',', '{comma}', $this->value);
$record->$fieldname = implode(",", $this->value);
} else {
$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() {
if($this->value&&is_array($this->value)){
// Filter items to those who aren't 0
if($this->value && is_array($this->value)) {
$filtered = array();
foreach($this->value as $item) if($item) $filtered[] = str_replace(",", "{comma}", $item);
return implode(",", $filtered);
} else {
return '';
foreach($this->value as $item) {
if($item) {
$filtered[] = str_replace(",", "{comma}", $item);
}
}
return implode(',', $filtered);
}
return '';
}
function performDisabledTransformation() {
$clone = clone $this;
$clone->setDisabled(true);
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() {
$values = '';
$data = array();
$items = $this->value;
foreach($this->source as $source) {
if(is_object($source)) {
$sourceTitles[$source->ID] = $source->Title;
if($this->source) {
foreach($this->source as $source) {
if(is_object($source)) {
$sourceTitles[$source->ID] = $source->Title;
}
}
}
if($items){
if($items) {
// Items is a DO Set
if(is_a($items,'DataObjectSet')){
foreach($items as $item){
if(is_a($items, 'DataObjectSet')) {
foreach($items as $item) {
$data[] = $item->Title;
}
if($data) {
$values = implode(", ",$data);
}
if($data) $values = implode(', ', $data);
// Items is an array or single piece of string (including comma seperated string)
}else{
} else {
if(!is_array($items)) {
$items = split(" *, *", trim($items));
$items = split(' *, *', trim($items));
}
foreach($items as $item){
foreach($items as $item) {
if(is_array($item)) {
$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];
} else if(is_a($this->source, "ComponentSet")){
//added for editable checkboxset.
} elseif(is_a($this->source, 'ComponentSet')) {
$data[] = $sourceTitles[$item];
} else {
$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);
return $field;
}
function ExtraOptions() {
return FormField::ExtraOptions();
}
}
}
?>
?>

View File

@ -472,7 +472,7 @@ JS;
if(!$childData->ID && $this->getParentClass()) {
// 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() );
$childData->$parentIDName = $childData->ID;
$childData->$parentIDName = $this->sourceID();
}
$detailFields = $this->getCustomFieldsFor($childData);
@ -725,8 +725,10 @@ class ComplexTableField_ItemRequest extends RequestHandler {
* @see Form::ReferencedField
*/
function saveComplexTableField($data, $form, $request) {
$form->saveInto($this->dataObj());
$this->dataObj()->write();
$dataObject = $this->dataObj();
$form->saveInto($dataObject);
$dataObject->write();
$closeLink = sprintf(
'<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(
_t('ComplexTableField.SUCCESSEDIT', 'Saved %s %s %s'),
$this->dataObj()->singular_name(),
'<a href="' . $this->Link() . '">"' . $this->dataObj()->Title . '"</a>',
$dataObject->singular_name(),
'<a href="' . $this->Link() . '">"' . $dataObject->Title . '"</a>',
$closeLink
);
$form->sessionMessage($message, 'good');
@ -753,8 +755,9 @@ class ComplexTableField_ItemRequest extends RequestHandler {
if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == 0) {
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;
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
}
@ -763,8 +766,9 @@ class ComplexTableField_ItemRequest extends RequestHandler {
if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->totalCount-1) {
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;
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
}
@ -774,7 +778,8 @@ class ComplexTableField_ItemRequest extends RequestHandler {
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;
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
@ -785,7 +790,8 @@ class ComplexTableField_ItemRequest extends RequestHandler {
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;
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");

View File

@ -175,4 +175,4 @@ class CompositeDateField_Disabled extends DateField {
return "date_disabled readonly";
}
}
?>
?>

View File

@ -28,4 +28,4 @@ class CountryDropdownField extends DropdownField {
}
}
?>
?>

View File

@ -112,15 +112,16 @@ class DateField_Disabled extends DateField {
$df->setValue($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 {
$val = Convert::raw2xml($this->value . ', ' . $df->Ago());
}
} 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() {
@ -139,4 +140,4 @@ class DateField_Disabled extends DateField {
return true;
}
}
?>
?>

View File

@ -83,7 +83,22 @@ class FieldGroup extends CompositeField {
}
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() {

View File

@ -535,4 +535,4 @@ class FieldSet extends DataObjectSet {
}
?>
?>

View File

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

View File

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

View File

@ -69,6 +69,10 @@ class FormAction extends FormField {
'type' => 'submit',
'name' => $this->action
);
if($this->isReadonly()) {
$attributes['disabled'] = 'disabled';
$attributes['class'] = $attributes['class'] . ' disabled';
}
return $this->createTag('button', $attributes, $this->attrTitle());
} else {
@ -79,7 +83,10 @@ class FormAction extends FormField {
'name' => $this->action,
'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();
return $this->createTag('input', $attributes);

View File

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

View File

@ -52,4 +52,4 @@ JS;
}
}
?>
?>

View File

@ -46,52 +46,6 @@ class HasManyComplexTableField extends ComplexTableField {
if($this->controller instanceof DataObject) return $this->controller->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() {
return $this->controller->ID;
@ -123,15 +77,21 @@ class HasManyComplexTableField extends ComplexTableField {
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() {
$items = array();
if($this->unpagedSourceItems) {
foreach($this->unpagedSourceItems as $item) {
if($item->{$this->joinField} == $this->controller->ID)
$items[] = $item->ID;
}
}
$list = implode(',', $items);
$list = implode(',', $this->selectedItemIDs());
$inputId = $this->id() . '_' . $this->htmlListEndName;
return <<<HTML
<input id="$inputId" name="{$this->name}[{$this->htmlListField}]" type="hidden" value="$list"/>
@ -166,4 +126,4 @@ class HasManyComplexTableField_Item extends ComplexTableField_Item {
}
}
?>
?>

View File

@ -15,7 +15,7 @@ class HtmlEditorField extends TextareaField {
/**
* 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);
$this->extraClass = 'typography';
}
@ -100,10 +100,10 @@ class HtmlEditorField extends TextareaField {
$content = preg_replace('/mce_real_src="[^"]+"/i', "", $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('src="([^\?]*)\?r=[0-9]+"','src="\\1"',$content);
$content = eregi_replace('mce_src="([^\?]*)\?r=[0-9]+"','mce_src="\\1"',$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('src="([^\?]*)\?r=[0-9]+"','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);
@ -111,11 +111,12 @@ class HtmlEditorField extends TextareaField {
if(!ereg("^[ \t\r\n]*<", $content)) $content = "<p>$content</p>";
$links = HTTP::getLinksIn($content);
$linkedPages = array();
if($links) foreach($links as $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);
if($candidatePage) {
$linkedPages[] = $candidatePage->ID;
@ -135,10 +136,8 @@ class HtmlEditorField extends TextareaField {
}
$images = HTTP::getImagesIn($content);
if($images){
if($images) {
foreach($images as $image) {
$image = Director::makeRelative($image);
if(substr($image,0,7) == 'assets/') {
$candidateImage = DataObject::get_one("File", "\"Filename\" = '$image'");
@ -150,7 +149,7 @@ class HtmlEditorField extends TextareaField {
$fieldName = $this->name;
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) {
$linkTracking->add($item, array("FieldName" => $fieldName));
@ -410,14 +409,14 @@ class HtmlEditorField_Toolbar extends RequestHandler {
new TreeDropdownField('FolderID', _t('HtmlEditorField.FOLDER', 'Folder'), 'Folder'),
new LiteralField('AddFolderOrUpload',
'<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">
<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="FolderOk" class="link addFolder">' . _t('HtmlEditorField.OK','Ok') . '</a>
<a style="display: none;" href="#" id="FolderCancel" class="link addFolder">' . _t('HtmlEditorField.FOLDERCANCEL','Cancel') . '</a>
</div>
<div id="PipeSeparator" style="display:inline">|</div>
<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>'
),
new TextField('getimagesSearch', _t('HtmlEditorField.SEARCHFILENAME', 'Search by file name')),

View File

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

View File

@ -33,4 +33,4 @@ class ImageFormAction extends FormAction {
}
}
?>
?>

View File

@ -80,4 +80,4 @@ class ListboxField extends DropdownField {
}
}
?>
?>

View File

@ -58,4 +58,4 @@ class LookupField extends DropdownField {
}
}
?>
?>

View File

@ -42,45 +42,15 @@ class ManyManyComplexTableField extends HasManyComplexTableField {
$this->joinField = 'Checked';
}
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;
}
$parent = $this->controllerClass();
$query->select[] = "IF(\"{$this->manyManyParentClass}ID\" IS NULL, '0', '1') AS Checked";
}
return clone $query;
function getQuery() {
$query = parent::getQuery();
$query->select[] = "IF(`{$this->manyManyParentClass}ID` IS NULL, '0', '1') AS Checked";
return $query;
}
function getParentIdName($parentClass, $childClass) {
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

@ -62,4 +62,4 @@ JS;
return (is_numeric($this->value)) ? $this->value : 0;
}
}
?>
?>

View File

@ -49,4 +49,4 @@ class PasswordField extends FormField {
}
}
?>
?>

View File

@ -189,4 +189,4 @@ JS;
return true;
}
}
?>
?>

View File

@ -6,6 +6,21 @@
*/
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() {
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 {
function Field() {
$titleAttr = $this->description ? "title=\"" . Convert::raw2att($this->description) . "\"" : '';
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 {
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

@ -118,4 +118,4 @@ class TabSet extends CompositeField {
parent::removeByName( $tabName, $dataFieldOnly );
}
}
?>
?>

View File

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

View File

@ -801,6 +801,7 @@ JS
}
function FirstItem() {
if ($this->TotalCount() < 1) return 0;
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).
* 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)
*/
@ -901,19 +904,28 @@ JS
$now = Date("d-m-Y-H-i");
$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;
$csvColumns = ($this->fieldListCsv) ? $this->fieldListCsv : $this->fieldList;
$fileData = "";
$fileData = '';
$columnData = array();
$fieldItems = new DataObjectSet();
if($this->csvHasHeader) {
$fileData .= "\"" . implode("\"{$separator}\"",array_values($csvColumns)) . "\"";
$fileData .= "\"" . implode("\"{$separator}\"", array_values($csvColumns)) . "\"";
$fileData .= "\n";
}
// get data
if(isset($this->customSourceItems)){
if(isset($this->customSourceItems)) {
$items = $this->customSourceItems;
}else{
} else {
$dataQuery = $this->getCsvQuery();
$records = $dataQuery->execute();
$sourceClass = $this->sourceClass;
@ -921,7 +933,6 @@ JS
$items = $dataobject->buildDataObjectSet($records, 'DataObjectSet');
}
$fieldItems = new DataObjectSet();
if($items && $items->count()) foreach($items as $item) {
// create a TableListField_Item to support resolving of
// relation-fields in dot notation via TableListField_Item->Fields()
@ -934,15 +945,14 @@ JS
if($fieldItems) {
foreach($fieldItems as $fieldItem) {
$columnData = array();
$fields = $fieldItem->Fields();
foreach($fields as $field) {
$columnData = array();
if($fields) foreach($fields as $field) {
$value = $field->Value;
// TODO This should be replaced with casting
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 = str_replace('__VAL__', '$value', $format);
eval('$value = "' . $format . '";');
@ -955,9 +965,12 @@ JS
$fileData .= implode($separator, $columnData);
$fileData .= "\n";
}
return HTTPRequest::send_file($fileData, $fileName);
$numColumns = count($columnData);
$numRows = $fieldItems->count();
return $fileData;
} else {
user_error("No records found", E_USER_ERROR);
return null;
}
}
@ -1416,4 +1429,4 @@ class TableListField_ItemRequest extends RequestHandler {
return false;
}
}
?>
?>

View File

@ -94,4 +94,4 @@ class TextareaField extends FormField {
return parent::Type() . ( $this->readonly ? ' readonly' : '' );
}
}
?>
?>

View File

@ -137,4 +137,4 @@ HTML;
}
?>
?>

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',
onComplete: function(response) {
Element.replace(this.id, response.responseText)
Behaviour.apply($(this.id))
Element.replace(this.id, response.responseText);
// reapply behaviour and reattach methods to TF container node
// e.g. <div class="TableListField">
Behaviour.apply($(this.id), true);
}.bind(this)
}
);

View File

@ -134,7 +134,7 @@ TreeDropdownField.prototype = {
this.treeShown = false;
if(this.itemTree) {
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.unstretchIframeIfNeeded();
}

View File

@ -57,7 +57,7 @@ if($majorVersion < 5) {
*/
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')) {
mb_http_output('UTF-8');
mb_internal_encoding('UTF-8');
@ -86,8 +86,7 @@ if (isset($_GET['url'])) {
}
// 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'])) {
Profiler::init();

View File

@ -165,4 +165,4 @@ class BBCodeParser extends TextParser {
}
}
?>
?>

View File

@ -213,17 +213,20 @@ class SSHTMLBBCodeParser
$filter = ucfirst($filter);
if (!array_key_exists($filter, $this->_filters)) {
$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)) {
//PEAR::raiseError("Failed to load filter $filter", null, PEAR_ERROR_DIE);
}
$this->_filters[$filter] = new $class;
$this->_definedTags = array_merge(
$this->_definedTags,
$this->_filters[$filter]->_definedTags
);
else {
$this->_filters[$filter] = new $class;
$this->_definedTags = array_merge(
$this->_definedTags,
$this->_filters[$filter]->_definedTags
);
}
}
}

View File

@ -242,4 +242,4 @@ function profiler_stop($name) {
$GLOBALS["midcom_profiler"]->stopTimer ($name);
}
?>
?>

13
sake
View File

@ -48,7 +48,7 @@ if [ "$1" = "-start" ]; then
echo "You need to install the 'daemon' tool. In debian, go 'sudo apt-get install daemon'"
exit 1
fi
if [ ! -f $base/$2.pid ]; then
echo "Starting service $2 $3"
touch $base/$2.pid
@ -61,11 +61,18 @@ if [ "$1" = "-start" ]; then
sake=`realpath $0`
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
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
echo "Service $2 seems to already be running"
fi
@ -89,4 +96,4 @@ fi
################################################################################################
## Basic execution
$php $sapphire/cli-script.php ${*}
$php $sapphire/cli-script.php ${*}

View File

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

View File

@ -31,6 +31,11 @@ class SearchForm extends Form {
*/
protected $pageLength = 10;
/**
* Classes to search
*/
protected $classesToSearch = array("SiteTree", "File");
/**
*
* @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.
* 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) {
if(!$pageLength) $pageLength = $this->pageLength;
$fileFilter = '';
$keywords = Convert::raw2sql($keywords);
$htmlEntityKeywords = htmlentities($keywords);
$extraFilters = array('SiteTree' => '', 'File' => '');
if($booleanSearch) $boolean = "IN BOOLEAN MODE";
if($extraFilter) {
$extraFilter = " AND $extraFilter";
$fileFilter = ($alternativeFileFilter) ? " AND $alternativeFileFilter" : $extraFilter;
$extraFilters['SiteTree'] = " AND $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;
$limit = $start . ", " . (int) $pageLength;
$notMatch = $invertedMatch ? "NOT " : "";
if($keywords) {
$matchContent = "
MATCH (Title, MenuTitle, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$keywords' $boolean)
+ MATCH (Content) AGAINST ('$htmlEntityKeywords' $boolean)
";
$matchFile = "MATCH (Filename, Title, Content) AGAINST ('$keywords' $boolean) AND ClassName = 'File'";
$match['SiteTree'] = "MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$keywords' $boolean)";
$match['File'] = "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
$relevanceKeywords = str_replace(array('*','+','-'),'',$keywords);
$htmlEntityRelevanceKeywords = str_replace(array('*','+','-'),'',$htmlEntityKeywords);
$relevanceContent = "
MATCH (Title) AGAINST ('$relevanceKeywords')
+ MATCH(Content) AGAINST ('$htmlEntityRelevanceKeywords')
+ MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$relevanceKeywords')
";
$relevanceFile = "MATCH (Filename, Title, Content) AGAINST ('$relevanceKeywords')";
$relevance['SiteTree'] = "MATCH (Title) AGAINST ('$relevanceKeywords') + MATCH(Content) AGAINST ('$htmlEntityRelevanceKeywords') + MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$relevanceKeywords')";
$relevance['File'] = "MATCH (Filename, Title, Content) AGAINST ('$relevanceKeywords')";
} else {
$relevanceContent = $relevanceFile = 1;
$matchContent = $matchFile = "1 = 1";
$relevance['SiteTree'] = $relevance['File'] = 1;
$match['SiteTree'] = $match['File'] = "1 = 1";
}
$queryContent = singleton('SiteTree')->extendedSQL($notMatch . $matchContent . $extraFilter, "");
$baseClass = reset($queryContent->from);
// There's no need to do all that joining
$queryContent->from = array(str_replace(array('`','"'),'',$baseClass) => $baseClass);
$queryContent->select = array("\"ClassName\"","$baseClass.\"ID\"","\"ParentID\"","\"Title\"",
"\"URLSegment\"","\"Content\"","\"LastEdited\"","\"Created\"","'' AS \"Filename\"",
"'' AS \"Name\"", "$relevanceContent AS \"Relevance\"", "\"CanViewType\"");
$queryContent->orderby = null;
$queryFiles = singleton('File')->extendedSQL($notMatch . $matchFile . $fileFilter, "");
$baseClass = reset($queryFiles->from);
// There's no need to do all that joining
$queryFiles->from = array(str_replace(array('`','"'),'',$baseClass) => $baseClass);
$queryFiles->select = array("\"ClassName\"","$baseClass.\"ID\"","'' AS \"ParentID\"","\"Title\"",
"'' AS \"URLSegment\"","\"Content\"","\"LastEdited\"","\"Created\"","\"Filename\"","\"Name\"",
"$relevanceFile AS \"Relevance\"","NULL AS \"CanViewType\"");
$queryFiles->orderby = null;
// 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);
}
$fullQuery = $queryContent->sql() . " UNION " . $queryFiles->sql() . " ORDER BY $sortBy LIMIT $limit";
$totalCount = $queryContent->unlimitedRowCount() + $queryFiles->unlimitedRowCount();
// Make column selection lists
$select = array(
'SiteTree' => array("ClassName","$baseClasses[SiteTree].ID","ParentID","Title","URLSegment","Content","LastEdited","Created","_utf8'' AS Filename", "_utf8'' AS Name", "$relevance[SiteTree] AS Relevance", "CanViewType"),
'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"),
);
// Process queries
foreach($this->classesToSearch as $class) {
// There's no need to do all that joining
$queries[$class]->from = array(str_replace('`','',$baseClasses[$class]) => $baseClasses[$class]);
$queries[$class]->select = $select[$class];
$queries[$class]->orderby = null;
}
// Combine queries
$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);
foreach($records as $record)
@ -254,4 +277,4 @@ class SearchForm extends Form {
}
?>
?>

View File

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

View File

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

View File

@ -278,6 +278,14 @@ class Group extends DataObject {
|| (Member::currentUserID() && !DataObject::get("Permission", "GroupID = $this->ID AND Code = 'ADMIN'"));
}
}
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.
@ -317,4 +325,4 @@ class Group extends DataObject {
}
}
?>
?>

View File

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

View File

@ -7,17 +7,16 @@
class Member extends DataObject {
static $db = array(
'FirstName' => "Varchar",
'Surname' => "Varchar",
'Email' => "Varchar",
'Password' => "Varchar(64)", // support for up to SHA256!
'RememberLoginToken' => "Varchar(50)",
'NumVisit' => "Int",
'FirstName' => 'Varchar',
'Surname' => 'Varchar',
'Email' => 'Varchar',
'Password' => 'Varchar(64)', // support for up to SHA256!
'RememberLoginToken' => 'Varchar(50)',
'NumVisit' => 'Int',
'LastVisited' => 'SSDatetime',
'Bounced' => 'Boolean', // Note: This does not seem to be used anywhere.
'AutoLoginHash' => 'Varchar(30)',
'AutoLoginExpired' => 'SSDatetime',
'BlacklistedEmail' => 'Boolean',
'PasswordEncryption' => "Enum('none', 'none')",
'Salt' => 'Varchar(50)',
'PasswordExpiry' => 'Date',
@ -26,11 +25,13 @@ class Member extends DataObject {
);
static $belongs_many_many = array(
"Groups" => "Group",
'Groups' => 'Group',
);
static $has_one = array();
static $has_many = array();
static $many_many = array();
static $many_many_extraFields = array();
@ -68,6 +69,15 @@ class Member extends DataObject {
'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
*/
@ -130,8 +140,30 @@ class Member extends DataObject {
// This can be called via CLI during testing.
if(Director::is_cli()) return;
$file = ""; $line = "";
if (!headers_sent($file, $line)) session_regenerate_id(true);
$file = '';
$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);
}
$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();
@ -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
* filesystem.
@ -437,29 +448,18 @@ class Member extends DataObject {
}
}
/**
* 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.
* Event handler called before writing to the database.
*/
function onBeforeWrite() {
if($this->SetPassword) $this->Password = $this->SetPassword;
if($this->Email) {
if($this->ID) {
$idClause = "AND \"Member\".\"ID\" <> $this->ID";
} else {
$idClause = "";
}
$existingRecord = DataObject::get_one(
"Member", "\"Email\" = '" . addslashes($this->Email) . "' $idClause");
// Debug::message("Found an existing member for email $this->Email");
$identifierField = self::$unique_identifier_field;
if($this->$identifierField) {
$idClause = ($this->ID) ? " AND \"Member\".\"ID\" <> $this->ID" : '';
$SQL_identifierField = Convert::raw2sql($this->$identifierField);
$existingRecord = DataObject::get_one('Member', "\"$identifierField\" = '{$SQL_identifierField}'{$idClause}");
if($existingRecord) {
$newID = $existingRecord->ID;
if($this->ID) {
@ -761,15 +761,13 @@ class Member extends DataObject {
$groupIDList = array();
if(is_a($groups, 'DataObjectSet')) {
foreach($groups as $group)
foreach($groups as $group) {
$groupIDList[] = $group->ID;
}
} elseif(is_array($groups)) {
$groupIDList = $groups;
}
/*if( empty( $groupIDList ) )
return Member::map(); */
$filterClause = ($groupIDList)
? "\"GroupID\" IN (" . implode( ',', $groupIDList ) . ")"
: "";
@ -792,8 +790,7 @@ class Member extends DataObject {
* @return array Groups in which the member is NOT in.
*/
public function memberNotInGroups($groupList, $memberGroups = null){
if(!$memberGroups)
$memberGroups = $this->Groups();
if(!$memberGroups) $memberGroups = $this->Groups();
foreach($memberGroups as $group) {
if(in_array($group->Code, $groupList)) {
@ -801,6 +798,7 @@ class Member extends DataObject {
unset($groupList[$index]);
}
}
return $groupList;
}
@ -846,11 +844,6 @@ class Member extends DataObject {
$locale
));
$mainFields->insertAfter(
new TreeMultiselectField("Groups", _t("Member.SECURITYGROUPS", "Security groups")),
'Locale'
);
$mainFields->removeByName('Bounced');
$mainFields->removeByName('RememberLoginToken');
$mainFields->removeByName('AutoLoginHash');
@ -861,14 +854,14 @@ class Member extends DataObject {
$mainFields->removeByName('Salt');
$mainFields->removeByName('NumVisit');
$mainFields->removeByName('LastVisited');
$mainFields->removeByName('BlacklistedEmail');
$fields->removeByName('Subscriptions');
$fields->removeByName('UnsubscribedRecords');
// Groups relation will get us into logical conflicts because
// Members are displayed within group edit form in SecurityAdmin
$fields->removeByName('Groups');
$this->extend('updateCMSFields', $fields);
return $fields;
}
@ -935,10 +928,6 @@ class Member extends DataObject {
if($valid->valid()) {
$this->AutoLoginHash = null;
$this->write();
// Emails will be sent by Member::onBeforeWrite().
//$this->sendinfo('changePassword', array('CleartextPassword' => $password));
}
return $valid;
@ -1371,27 +1360,36 @@ class Member_Validator extends RequiredFields {
*/
function php($data) {
$valid = parent::php($data);
$identifierField = Member::get_unique_identifier_field();
$SQL_identifierField = Convert::raw2sql($data[$identifierField]);
$member = DataObject::get_one('Member', "\"$identifierField\" = '{$SQL_identifierField}'");
$member = DataObject::get_one('Member',
"\"Email\" = '". Convert::raw2sql($data['Email']) ."'");
// if we are in a complex table field popup, use ctf[childID], else use
// ID
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'];
elseif(isset($_REQUEST['ID']))
} elseif(isset($_REQUEST['ID'])) {
$id = $_REQUEST['ID'];
else
} else {
$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
if($this->extension_instances) {
@ -1402,7 +1400,6 @@ class Member_Validator extends RequiredFields {
}
}
return $valid;
}
@ -1429,9 +1426,7 @@ class Member_Validator extends RequiredFields {
return $js;
}
}
// Initialize the static DB variables to add the supported encryption
// algorithms to the PasswordEncryption Enum field
Member::init_db_fields();
?>

View File

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

View File

@ -36,6 +36,15 @@ class MemberLoginForm extends LoginForm {
if(Director::fileExists($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'])) {
$backURL = $_REQUEST['BackURL'];
@ -50,8 +59,8 @@ class MemberLoginForm extends LoginForm {
if(!$fields) {
$fields = new FieldSet(
new HiddenField("AuthenticationMethod", null, $this->authenticator_class, $this),
new TextField("Email", _t('Member.EMAIL'), Session::get('SessionForms.MemberLoginForm.Email'), null, $this),
new PasswordField("Password", _t('Member.PASSWORD'), null, $this)
new TextField("Email", _t('Member.EMAIL', 'Email'), Session::get('SessionForms.MemberLoginForm.Email'), null, $this),
new PasswordField("Password", _t('Member.PASSWORD', 'Password'))
);
if(Security::$autologin_enabled) {
$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(
'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.
* 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(
"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
@ -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
* @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();
$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)) {
user_error(
@ -134,6 +155,8 @@ class Permission extends DataObject {
}
*/
$groupList = self::groupList($memberID);
if(!$groupList) return false;
@ -176,9 +199,11 @@ class Permission extends DataObject {
$argClause
)
")->value();
if($permission)
if($permission) {
self::$cache_permissions[$memberID][$codeStr] = $permission;
return $permission;
}
// Strict checking disabled?
if(!self::$strict_checking || !$strict) {
@ -190,11 +215,14 @@ class Permission extends DataObject {
AND (\"Type\" = " . self::GRANT_PERMISSION . ")
)
")->value();
if(!$hasPermission) {
self::$cache_permissions[$memberID][$codeStr] = true;
return true;
}
}
self::$cache_permissions[$memberID][$codeStr] = 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
if(Member::currentUserID()) {
// user_error( 'PermFailure with member', E_USER_ERROR );
$message = isset($messageSet['alreadyLoggedIn'])
? $messageSet['alreadyLoggedIn']
: $messageSet['default'];
if($member = Member::currentUser())
$member->logout();
$message = isset($messageSet['alreadyLoggedIn']) ? $messageSet['alreadyLoggedIn'] : $messageSet['default'];
if($member = Member::currentUser()) {
$member->logOut();
}
} else if(substr(Director::history(),0,15) == 'Security/logout') {
$message = $messageSet['logInAgain']
? $messageSet['logInAgain']
: $messageSet['default'];
$message = $messageSet['logInAgain'] ? $messageSet['logInAgain'] : $messageSet['default'];
} else {
$message = $messageSet['default'];
}
@ -356,11 +348,7 @@ class Security extends Controller {
Session::clear('Security.Message');
// custom processing
if(SSViewer::hasTemplate("Security_login")) {
return $customisedController->renderWith(array("Security_login", $this->stat('template_main')));
} else {
return $customisedController->renderWith($this->stat('template_main'));
}
return $customisedController->renderWith(array('Security_login', 'Security', $this->stat('template_main')));
}
function basicauthlogin() {
@ -398,7 +386,7 @@ class Security extends 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,
'LostPasswordForm',
new FieldSet(
new EmailField('Email', _t('Member.EMAIL'))
new EmailField('Email', _t('Member.EMAIL', 'Email'))
),
new FieldSet(
new FormAction(
@ -456,7 +444,7 @@ class Security extends 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;
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

@ -12,4 +12,4 @@
class HourlyTask extends ScheduledTask {
}
?>
?>

View File

@ -12,4 +12,4 @@
class MonthlyTask extends ScheduledTask {
}
?>
?>

View File

@ -24,4 +24,4 @@
abstract class ScheduledTask extends CliController {
// this class exists as a logical extension
}
?>
?>

View File

@ -64,7 +64,7 @@
<% end_control %>
<% 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>
<% end_if %>
<% end_control %>
</tr>
<% end_control %>
<% 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 %>
<table class="data">
<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 {
/**
* 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()}
*/

View File

@ -57,6 +57,7 @@ class RestfulServiceTest extends SapphireTest {
class RestfulServiceTest_Controller extends Controller {
public function index() {
ContentNegotiator::disable();
BasicAuth::disable();
$request_count = count($_REQUEST);
$get_count = count($_GET);
$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');
// 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->assertEquals("He's a good guy", $obj->Biography);
$this->assertEquals("1988-01-31", $obj->Birthday);
$this->assertEquals("1", $obj->IsRegistered);
fclose($file);
}
@ -44,7 +45,8 @@ class CsvBulkLoaderTest extends SapphireTest {
'FirstName',
'Biography',
null, // ignored column
'Birthday'
'Birthday',
'IsRegistered'
);
$loader->hasHeaderRow = false;
$results = $loader->load($filepath);
@ -53,10 +55,15 @@ class CsvBulkLoaderTest extends SapphireTest {
$this->assertEquals(4, $results->Count(), 'Test correct count of imported data');
// 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->assertEquals("He's a good guy", $obj->Biography);
$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);
}
@ -133,7 +140,6 @@ class CsvBulkLoaderTest extends SapphireTest {
$player = DataObject::get_by_id('CsvBulkLoaderTest_Player', 1);
$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');
}
function testLoadWithCustomImportMethods() {
@ -142,12 +148,14 @@ class CsvBulkLoaderTest extends SapphireTest {
$loader->columnMap = array(
'FirstName' => '->importFirstName',
'Biography' => 'Biography',
'Birthday' => 'Birthday'
'Birthday' => 'Birthday',
'IsRegistered' => 'IsRegistered'
);
$results = $loader->load($filepath);
$player = DataObject::get_by_id('CsvBulkLoaderTest_Player', 1);
$this->assertEquals($player->FirstName, 'Customized John');
$this->assertEquals($player->Biography, "He's a good guy");
$this->assertEquals($player->IsRegistered, "1");
}
protected function getLineCount(&$file) {
@ -185,6 +193,7 @@ class CsvBulkLoaderTest_Player extends DataObject implements TestOnly {
'Biography' => 'HTMLText',
'Birthday' => 'Date',
'ExternalIdentifier' => 'Varchar(255)', // used for uniqueness checks on passed property
'IsRegistered' => 'Boolean'
);
static $has_one = array(
@ -215,4 +224,4 @@ class CsvBulkLoaderTest_PlayerContract extends DataObject implements TestOnly {
);
}
?>
?>

View File

@ -1,4 +1 @@
"John","He's a good guy","ignored","31/01/1988"
"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"
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
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"
"John","He's a good guy","31/01/1988"
"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"
FirstName,Biography,Birthday,IsRegistered John,He's a good guy,31/01/88,1 Jane,"She is awesome.
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
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

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

View File

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