diff --git a/_config.php b/_config.php index 5542c8b70..41198ca91 100644 --- a/_config.php +++ b/_config.php @@ -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'); - - - ?> \ No newline at end of file diff --git a/api/RSSFeed.php b/api/RSSFeed.php index 273c10169..73a88396e 100755 --- a/api/RSSFeed.php +++ b/api/RSSFeed.php @@ -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); } } -?> +?> \ No newline at end of file diff --git a/api/RestfulServer.php b/api/RestfulServer.php index a861ff6ba..e764bf3f8 100644 --- a/api/RestfulServer.php +++ b/api/RestfulServer.php @@ -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(); diff --git a/api/RestfulService.php b/api/RestfulService.php index 7f007e9cc..b64d8584e 100644 --- a/api/RestfulService.php +++ b/api/RestfulService.php @@ -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 { } } -?> \ No newline at end of file +?> diff --git a/api/XMLDataFormatter.php b/api/XMLDataFormatter.php index 150ba31c8..d9f5f64c2 100644 --- a/api/XMLDataFormatter.php +++ b/api/XMLDataFormatter.php @@ -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 "\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 = "\n"; diff --git a/cli/CliController.php b/cli/CliController.php index 12bc2f1cf..260b5b317 100755 --- a/cli/CliController.php +++ b/cli/CliController.php @@ -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() {} -} -?> \ No newline at end of file +} + +?> diff --git a/core/ClassInfo.php b/core/ClassInfo.php index 9e497cc99..747be4339 100755 --- a/core/ClassInfo.php +++ b/core/ClassInfo.php @@ -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: + * + * ClassInfo::subclassesFor('BaseClass'); + * array( + * 0 => 'BaseClass', + * 'ChildClass' => 'ChildClass', + * 'GrandChildClass' => 'GrandChildClass' + * ) + * * * @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; } diff --git a/core/Core.php b/core/Core.php index 1308f8166..fbd913269 100755 --- a/core/Core.php +++ b/core/Core.php @@ -286,5 +286,4 @@ function _t($entity, $string = "", $priority = 40, $context = "") { return i18n::_t($entity, $string, $priority, $context); } - ?> \ No newline at end of file diff --git a/core/HTTP.php b/core/HTTP.php index d2672b168..6220d2790 100644 --- a/core/HTTP.php +++ b/core/HTTP.php @@ -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 { } - -?> +?> \ No newline at end of file diff --git a/core/ManifestBuilder.php b/core/ManifestBuilder.php index 16a92069f..f40da439c 100644 --- a/core/ManifestBuilder.php +++ b/core/ManifestBuilder.php @@ -549,5 +549,4 @@ class ManifestBuilder { } } - -?> +?> \ No newline at end of file diff --git a/core/Object.php b/core/Object.php index a08a6c294..80c5a2d00 100755 --- a/core/Object.php +++ b/core/Object.php @@ -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; } } -?> +?> \ No newline at end of file diff --git a/core/Requirements.php b/core/Requirements.php index 1f0361952..5aea2f2bc 100644 --- a/core/Requirements.php +++ b/core/Requirements.php @@ -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 \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 { } - ?> diff --git a/core/SSViewer.php b/core/SSViewer.php index a1770704d..41ac4470f 100644 --- a/core/SSViewer.php +++ b/core/SSViewer.php @@ -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, "\n" . SSViewer::getTemplateContent($matches[1]) - . "\n";'; + . "\n";'; 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 ""; } -?> +?> \ No newline at end of file diff --git a/core/Session.php b/core/Session.php index 557543f6b..f52975197 100644 --- a/core/Session.php +++ b/core/Session.php @@ -236,4 +236,4 @@ class Session { return self::$timeout; } } -?> +?> \ No newline at end of file diff --git a/core/ViewableData.php b/core/ViewableData.php index ef60e3444..7a07c6037 100644 --- a/core/ViewableData.php +++ b/core/ViewableData.php @@ -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 { } } -?> +?> \ No newline at end of file diff --git a/core/control/ContentController.php b/core/control/ContentController.php index 6f9498863..50f2b6d84 100644 --- a/core/control/ContentController.php +++ b/core/control/ContentController.php @@ -224,7 +224,7 @@ JS if($this->dataRecord){ $thisPage = $this->dataRecord->Link(); $cmsLink = 'admin/show/' . $this->dataRecord->ID; - $cmsLink = "CMS"; + $cmsLink = "". _t('ContentController.CMS', 'CMS') .""; } 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 = "Archived Site"; - $liveLink = "Published Site"; - $stageLink = "Draft Site"; - $message = "
Archived site from
" . $dateObj->Nice() . "
"; + $archiveLink = "". _t('ContentController.ARCHIVEDSITE', 'Archived Site') .""; + $liveLink = "". _t('ContentController.PUBLISHEDSITE', 'Published Site') .""; + $stageLink = "". _t('ContentController.DRAFTSITE', 'Draft Site') .""; + $message = "
". _t('ContentController.ARCHIVEDSITEFROM', 'Archived site from') ."
" . $dateObj->Nice() . "
"; } else if(Versioned::current_stage() == 'Stage') { - $stageLink = "Draft Site"; - $liveLink = "Published Site"; - $message = "
DRAFT SITE
"; + $stageLink = "". _t('ContentController.DRAFTSITE', 'Draft Site') .""; + $liveLink = "". _t('ContentController.PUBLISHEDSITE', 'Published Site') .""; + $message = "
". _t('ContentController.DRAFTSITE', 'Draft Site') ."
"; } else { - $liveLink = "Published Site"; - $stageLink = "Draft Site"; - $message = "
PUBLISHED SITE
"; + $liveLink = "". _t('ContentController.PUBLISHEDSITE', 'Published Site') .""; + $stageLink = "". _t('ContentController.DRAFTSITE', 'Draft Site') .""; + $message = "
". _t('ContentController.PUBLISHEDSITE', 'Published Site') ."
"; } if($member) { $firstname = Convert::raw2xml($member->FirstName); $surname = Convert::raw2xml($member->Surame); - $logInMessage = "Logged in as {$firstname} {$surname} - log out"; + $logInMessage = _t('ContentController.LOGGEDINAS', 'Logged in as') ." {$firstname} {$surname} - ". _t('ContentController.LOGOUT', 'Log out'). ""; } else { - $logInMessage = "Not logged in - log in"; + $logInMessage = _t('ContentController.NOTLOGGEDIN', 'Not logged in') ." - ". _t('ContentController.LOGIN', 'Login') .""; } - + $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
-
View page in:
+
$viewPageIn
$cmsLink $stageLink
@@ -295,7 +295,7 @@ HTML; Requirements::css(SAPPHIRE_DIR . '/css/SilverStripeNavigator.css'); $dateObj = Object::create('Datetime', $date, null); // $dateObj->setVal($date); - return "
Archived site from
" . $dateObj->Nice() . "
"; + return "
". _t('ContentController.ARCHIVEDSITEFROM') ."
" . $dateObj->Nice() . "
"; } } } @@ -423,5 +423,4 @@ HTML } } - -?> +?> \ No newline at end of file diff --git a/core/control/ContentNegotiator.php b/core/control/ContentNegotiator.php index e3555f5c1..116bf59e8 100755 --- a/core/control/ContentNegotiator.php +++ b/core/control/ContentNegotiator.php @@ -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(' ',' ', $content); @@ -134,7 +134,7 @@ class ContentNegotiator { * Removes "xmlns" attributes and any 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'); } } -?> +?> \ No newline at end of file diff --git a/core/control/Controller.php b/core/control/Controller.php index 96c3ae08b..b363e4a17 100644 --- a/core/control/Controller.php +++ b/core/control/Controller.php @@ -544,4 +544,4 @@ class Controller extends RequestHandler { } } -?> +?> \ No newline at end of file diff --git a/core/control/HTTPRequest.php b/core/control/HTTPRequest.php index 796704308..d120bf57f 100644 --- a/core/control/HTTPRequest.php +++ b/core/control/HTTPRequest.php @@ -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; } /** diff --git a/core/control/ModelAsController.php b/core/control/ModelAsController.php index 178626f7e..925b48dcb 100644 --- a/core/control/ModelAsController.php +++ b/core/control/ModelAsController.php @@ -102,4 +102,4 @@ class ModelAsController extends Controller implements NestedController { } } -?> +?> \ No newline at end of file diff --git a/core/control/RootURLController.php b/core/control/RootURLController.php index 82fc583fe..60d4bad15 100755 --- a/core/control/RootURLController.php +++ b/core/control/RootURLController.php @@ -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); diff --git a/core/i18n.php b/core/i18n.php index 5a48ddf39..07d2632a3 100755 --- a/core/i18n.php +++ b/core/i18n.php @@ -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(); + } //-----------------------------------------------------------------------------------------------// diff --git a/core/i18nEntityProvider.php b/core/i18nEntityProvider.php index 645cdc380..7c7e4fec3 100644 --- a/core/i18nEntityProvider.php +++ b/core/i18nEntityProvider.php @@ -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: + * + * class MyTestClass implements i18nEntityProvider { + * function provideI18nEntities() { + * $entities = array(); + * $entities["MyOtherModuleClass.MYENTITY"] = array( + * $value, + * PR_MEDIUM, + * 'My context description', + * 'myothermodule' + * ); + * } + * return $entities; + * } + * + * * @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. diff --git a/core/i18nTextCollector.php b/core/i18nTextCollector.php index 126a3bab7..e0cddd508 100644 --- a/core/i18nTextCollector.php +++ b/core/i18nTextCollector.php @@ -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()); } diff --git a/core/model/DB.php b/core/model/DB.php index f006fc038..19be4b36d 100755 --- a/core/model/DB.php +++ b/core/model/DB.php @@ -262,4 +262,4 @@ class DB { return DB::$globalConn->quiet(); } } -?> +?> \ No newline at end of file diff --git a/core/model/DataObject.php b/core/model/DataObject.php index 97bdbe332..3ebf6c012 100644 --- a/core/model/DataObject.php +++ b/core/model/DataObject.php @@ -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 } -?> +?> \ No newline at end of file diff --git a/core/model/DataObjectDecorator.php b/core/model/DataObjectDecorator.php index de8519371..d69560001 100755 --- a/core/model/DataObjectDecorator.php +++ b/core/model/DataObjectDecorator.php @@ -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 { } } - -?> +?> \ No newline at end of file diff --git a/core/model/DataObjectSet.php b/core/model/DataObjectSet.php index 3385fc91d..bb62d50e1 100644 --- a/core/model/DataObjectSet.php +++ b/core/model/DataObjectSet.php @@ -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: + * + * <% if MyPages.MoreThanOnePage %> + * <% if MyPages.NotFirstPage %> + * + * <% end_if %> + * <% control MyPages.PaginationSummary(4) %> + * <% if CurrentBool %> + * $PageNum + * <% else %> + * <% if Link %> + * $PageNum + * <% else %> + * ... + * <% end_if %> + * <% end_if %> + * <% end_control %> + * <% if MyPages.NotLastPage %> + * + * <% end_if %> + * <% end_if %> + * + * + * @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 { } } -?> +?> \ No newline at end of file diff --git a/core/model/Database.php b/core/model/Database.php index bfcce0eba..a4ee06694 100755 --- a/core/model/Database.php +++ b/core/model/Database.php @@ -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); diff --git a/core/model/ErrorPage.php b/core/model/ErrorPage.php index 11c30e65e..9db4f9128 100755 --- a/core/model/ErrorPage.php +++ b/core/model/ErrorPage.php @@ -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. diff --git a/core/model/Hierarchy.php b/core/model/Hierarchy.php index 74b063cc1..16456073c 100644 --- a/core/model/Hierarchy.php +++ b/core/model/Hierarchy.php @@ -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; } /** diff --git a/core/model/Image.php b/core/model/Image.php index c92e0e6ab..91310ecb7 100755 --- a/core/model/Image.php +++ b/core/model/Image.php @@ -780,4 +780,4 @@ class Image_Uploader extends Controller { } } -?> +?> \ No newline at end of file diff --git a/core/model/MySQLDatabase.php b/core/model/MySQLDatabase.php index dec6f3f8a..756000eb4 100644 --- a/core/model/MySQLDatabase.php +++ b/core/model/MySQLDatabase.php @@ -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'; } /** diff --git a/core/model/PDODatabase.php b/core/model/PDODatabase.php index 1a9155586..e70769932 100644 --- a/core/model/PDODatabase.php +++ b/core/model/PDODatabase.php @@ -727,5 +727,4 @@ class PDOQuery extends Query { } } - -?> +?> \ No newline at end of file diff --git a/core/model/SQLQuery.php b/core/model/SQLQuery.php index e50eeceda..ff3f55754 100755 --- a/core/model/SQLQuery.php +++ b/core/model/SQLQuery.php @@ -452,4 +452,4 @@ class SQLQuery extends Object { } } -?> +?> \ No newline at end of file diff --git a/core/model/SiteTree.php b/core/model/SiteTree.php index efdb81ea3..feb09758f 100644 --- a/core/model/SiteTree.php +++ b/core/model/SiteTree.php @@ -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 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) . "\n"; } - $tags .= "\n"; + $version = new SapphireInfo(); + + $tags .= "Version() ." - http://www.silverstripe.com\" />\n"; $charset = ContentNegotiator::get_encoding(); $tags .= "\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[] = "
  • ID\">" . - $link->Breadcrumbs(null,true) . "
  • "; - } - $backlinks = "
    - " . _t('SiteTree.PAGESLINKING', 'The following pages link to this page:') . - "
      " . implode("",$backlinks) . "
    "; - } - } - - if(!isset($backlinks)) { - $backlinks = "

    " . _t('SiteTree.NOBACKLINKS', 'This page hasn\'t been linked to from any pages.') . "

    "; - } - - // 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("
    ", $statusMessage); } + $backLinksNote = ''; + $backLinksTable = new LiteralField('BackLinksNote', '

    ' . _t('NOBACKLINKEDPAGES', 'There are no pages linked to this page.') . '

    '); + + // Create a table for showing pages linked to this one + if($this->BackLinkTracking() && $this->BackLinkTracking()->Count() > 0) { + $backLinksNote = new LiteralField('BackLinksNote', '

    ' . _t('SiteTree.PAGESLINKING', 'The following pages link to this page:') . '

    '); + $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' => '$Title' + )); + $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 } } -?> +?> \ No newline at end of file diff --git a/core/model/Versioned.php b/core/model/Versioned.php index fb5f467ee..8e05d98d9 100755 --- a/core/model/Versioned.php +++ b/core/model/Versioned.php @@ -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 { } } -?> \ No newline at end of file +?> diff --git a/core/model/VirtualPage.php b/core/model/VirtualPage.php index 35eb033da..84960901e 100755 --- a/core/model/VirtualPage.php +++ b/core/model/VirtualPage.php @@ -170,4 +170,4 @@ class VirtualPage_Controller extends Page_Controller { } } -?> +?> \ No newline at end of file diff --git a/core/model/fieldtypes/DBField.php b/core/model/fieldtypes/DBField.php index 183ccaf7d..cb27fe49d 100644 --- a/core/model/fieldtypes/DBField.php +++ b/core/model/fieldtypes/DBField.php @@ -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; diff --git a/core/model/fieldtypes/Decimal.php b/core/model/fieldtypes/Decimal.php index 6d0184461..42f08ceb9 100644 --- a/core/model/fieldtypes/Decimal.php +++ b/core/model/fieldtypes/Decimal.php @@ -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. diff --git a/core/model/fieldtypes/Int.php b/core/model/fieldtypes/Int.php index 964de2896..25217af64 100644 --- a/core/model/fieldtypes/Int.php +++ b/core/model/fieldtypes/Int.php @@ -60,4 +60,4 @@ class Int extends DBField { } -?> +?> \ No newline at end of file diff --git a/core/model/fieldtypes/SSDatetime.php b/core/model/fieldtypes/SSDatetime.php index cd6a8c123..85e4711dc 100644 --- a/core/model/fieldtypes/SSDatetime.php +++ b/core/model/fieldtypes/SSDatetime.php @@ -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 { } } -?> +?> \ No newline at end of file diff --git a/core/model/fieldtypes/Text.php b/core/model/fieldtypes/Text.php index 5f06ca58c..e5ba404f5 100644 --- a/core/model/fieldtypes/Text.php +++ b/core/model/fieldtypes/Text.php @@ -321,4 +321,4 @@ class Text extends DBField { } } -?> +?> \ No newline at end of file diff --git a/core/model/fieldtypes/Varchar.php b/core/model/fieldtypes/Varchar.php index 81e32c93e..b9c46ab9d 100644 --- a/core/model/fieldtypes/Varchar.php +++ b/core/model/fieldtypes/Varchar.php @@ -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; } } -?> +?> \ No newline at end of file diff --git a/css/CalendarDateField.css b/css/CalendarDateField.css index e59be77ee..7e0a075f6 100755 --- a/css/CalendarDateField.css +++ b/css/CalendarDateField.css @@ -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; } diff --git a/css/ComplexTableField_popup.css b/css/ComplexTableField_popup.css index acf26b69d..e2e071f5b 100755 --- a/css/ComplexTableField_popup.css +++ b/css/ComplexTableField_popup.css @@ -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 { diff --git a/css/Form.css b/css/Form.css index ec5f8e307..38053fd29 100644 --- a/css/Form.css +++ b/css/Form.css @@ -137,4 +137,24 @@ form .message { color:#FF4040; width:240px; border-color: #FF4040; - } \ No newline at end of file + } + + /** 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; +} \ No newline at end of file diff --git a/css/MemberProfileForm.css b/css/MemberProfileForm.css index 71054fcbb..d81f6dbb0 100644 --- a/css/MemberProfileForm.css +++ b/css/MemberProfileForm.css @@ -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; } \ No newline at end of file diff --git a/css/SilverStripeNavigator.css b/css/SilverStripeNavigator.css index d39246a34..d39cfc7e0 100755 --- a/css/SilverStripeNavigator.css +++ b/css/SilverStripeNavigator.css @@ -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; } \ No newline at end of file diff --git a/css/TableListField.css b/css/TableListField.css index 42a99c741..c6f42d80c 100644 --- a/css/TableListField.css +++ b/css/TableListField.css @@ -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; } diff --git a/dev/BulkLoader.php b/dev/BulkLoader.php index 1cf380a10..261f433a6 100644 --- a/dev/BulkLoader.php +++ b/dev/BulkLoader.php @@ -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". * * @@ -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'); } } diff --git a/dev/DevelopmentAdmin.php b/dev/DevelopmentAdmin.php index 4dce4b7a3..a40eb3b98 100644 --- a/dev/DevelopmentAdmin.php +++ b/dev/DevelopmentAdmin.php @@ -130,4 +130,4 @@ class DevelopmentAdmin extends Controller { } } -?> +?> \ No newline at end of file diff --git a/dev/InstallerTest.php b/dev/InstallerTest.php index f35f972e1..b061235b3 100644 --- a/dev/InstallerTest.php +++ b/dev/InstallerTest.php @@ -11,4 +11,4 @@ class InstallerTest extends Controller { } } -?> +?> \ No newline at end of file diff --git a/dev/ModelViewer.php b/dev/ModelViewer.php index d85dcabd2..4f5b7f358 100644 --- a/dev/ModelViewer.php +++ b/dev/ModelViewer.php @@ -171,5 +171,4 @@ class ModelViewer_Relation extends ViewableData { } - -?> +?> \ No newline at end of file diff --git a/dev/SSCli.php b/dev/SSCli.php index 6987e1cd8..008294619 100644 --- a/dev/SSCli.php +++ b/dev/SSCli.php @@ -76,4 +76,4 @@ class SSCli extends Object { } } -?> +?> \ No newline at end of file diff --git a/dev/SapphireTest.php b/dev/SapphireTest.php index dc63cc0a7..4a290a69d 100644 --- a/dev/SapphireTest.php +++ b/dev/SapphireTest.php @@ -236,4 +236,4 @@ class SapphireTest extends PHPUnit_Framework_TestCase { } } -?> +?> \ No newline at end of file diff --git a/email/Email.php b/email/Email.php index b287cc11f..257e63e09 100755 --- a/email/Email.php +++ b/email/Email.php @@ -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 '' . strrev($email) . ''; 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; - } - } -} - -?> +?> \ No newline at end of file diff --git a/email/Mailer.php b/email/Mailer.php index 2b635f3ce..2c553d5f8 100644 --- a/email/Mailer.php +++ b/email/Mailer.php @@ -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; } - diff --git a/email/QueuedEmail.php b/email/QueuedEmail.php index e8cdd6693..72702ebf3 100644 --- a/email/QueuedEmail.php +++ b/email/QueuedEmail.php @@ -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(); } } -?> +?> \ No newline at end of file diff --git a/filesystem/File.php b/filesystem/File.php index 19583e0fb..bf7c0e858 100755 --- a/filesystem/File.php +++ b/filesystem/File.php @@ -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. diff --git a/filesystem/Filesystem.php b/filesystem/Filesystem.php index 786ac1015..6e5ed4506 100755 --- a/filesystem/Filesystem.php +++ b/filesystem/Filesystem.php @@ -124,4 +124,4 @@ class Filesystem extends Object { } -?> +?> \ No newline at end of file diff --git a/filesystem/Folder.php b/filesystem/Folder.php index 3e82f1423..4aa6cad58 100755 --- a/filesystem/Folder.php +++ b/filesystem/Folder.php @@ -441,11 +441,11 @@ class Folder extends File { */ function getUploadIframe() { return << + HTML; } } -?> +?> \ No newline at end of file diff --git a/filesystem/GD.php b/filesystem/GD.php index b6b602311..43c0afa97 100755 --- a/filesystem/GD.php +++ b/filesystem/GD.php @@ -408,4 +408,4 @@ class GD extends Object { } -?> +?> \ No newline at end of file diff --git a/forms/CheckboxSetField.php b/forms/CheckboxSetField.php index cd5f9c944..a1531e362 100755 --- a/forms/CheckboxSetField.php +++ b/forms/CheckboxSetField.php @@ -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 .= "
  • name[$key]\" type=\"checkbox\" value=\"$key\"$checked $disabled class=\"checkbox\" />
  • \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(); - } + } + } -?> +?> \ No newline at end of file diff --git a/forms/ComplexTableField.php b/forms/ComplexTableField.php index 104f30621..13c1dc951 100755 --- a/forms/ComplexTableField.php +++ b/forms/ComplexTableField.php @@ -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( '(%s)', @@ -734,8 +736,8 @@ class ComplexTableField_ItemRequest extends RequestHandler { ); $message = sprintf( _t('ComplexTableField.SUCCESSEDIT', 'Saved %s %s %s'), - $this->dataObj()->singular_name(), - '"' . $this->dataObj()->Title . '"', + $dataObject->singular_name(), + '"' . $dataObject->Title . '"', $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}"); diff --git a/forms/CompositeDateField.php b/forms/CompositeDateField.php index 3b52407a9..294c8de7a 100755 --- a/forms/CompositeDateField.php +++ b/forms/CompositeDateField.php @@ -175,4 +175,4 @@ class CompositeDateField_Disabled extends DateField { return "date_disabled readonly"; } } -?> +?> \ No newline at end of file diff --git a/forms/CountryDropdownField.php b/forms/CountryDropdownField.php index 2b13786b8..74e0606b6 100644 --- a/forms/CountryDropdownField.php +++ b/forms/CountryDropdownField.php @@ -28,4 +28,4 @@ class CountryDropdownField extends DropdownField { } } -?> +?> \ No newline at end of file diff --git a/forms/DateField.php b/forms/DateField.php index 524c79c48..92f26bf5b 100755 --- a/forms/DateField.php +++ b/forms/DateField.php @@ -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 = '('._t('DateField.NOTSET', 'not set').')'; + $val = '('._t('DateField.NOTSET', 'not set').')'; } - return "id() . "\">$val"; + return "id() . "\">$val + value}\" name=\"$this->name\" />"; } function Type() { @@ -139,4 +140,4 @@ class DateField_Disabled extends DateField { return true; } } -?> +?> \ No newline at end of file diff --git a/forms/FieldGroup.php b/forms/FieldGroup.php index 96dd4ef68..e40165346 100755 --- a/forms/FieldGroup.php +++ b/forms/FieldGroup.php @@ -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)) ? "" : ""; + $messageBlock = (!empty($Message)) ? "$Message" : ""; + $rightTitleBlock = (!empty($RightTitle)) ? "" : ""; + + return <<$titleBlock
    $Field
    $rightTitleBlock$messageBlock
    +HTML; } function Message() { diff --git a/forms/FieldSet.php b/forms/FieldSet.php index 218148b2e..6d5e13778 100755 --- a/forms/FieldSet.php +++ b/forms/FieldSet.php @@ -535,4 +535,4 @@ class FieldSet extends DataObjectSet { } -?> +?> \ No newline at end of file diff --git a/forms/FileField.php b/forms/FileField.php index 3a824ec1a..b8027d0ad 100755 --- a/forms/FileField.php +++ b/forms/FileField.php @@ -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; } } -?> +?> \ No newline at end of file diff --git a/forms/Form.php b/forms/Form.php index 6448ffb0a..f8aafab77 100644 --- a/forms/Form.php +++ b/forms/Form.php @@ -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()) { diff --git a/forms/FormAction.php b/forms/FormAction.php index c6c946888..d649c31db 100755 --- a/forms/FormAction.php +++ b/forms/FormAction.php @@ -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); diff --git a/forms/FormField.php b/forms/FormField.php index 791bdb5ab..a103d22c6 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -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. diff --git a/forms/GSTNumberField.php b/forms/GSTNumberField.php index 20447872d..46ec56087 100755 --- a/forms/GSTNumberField.php +++ b/forms/GSTNumberField.php @@ -52,4 +52,4 @@ JS; } } -?> +?> \ No newline at end of file diff --git a/forms/HasManyComplexTableField.php b/forms/HasManyComplexTableField.php index ab6ede7e0..008fe2bcb 100644 --- a/forms/HasManyComplexTableField.php +++ b/forms/HasManyComplexTableField.php @@ -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 << @@ -166,4 +126,4 @@ class HasManyComplexTableField_Item extends ComplexTableField_Item { } } -?> \ No newline at end of file +?> diff --git a/forms/HtmlEditorField.php b/forms/HtmlEditorField.php index 5e950b710..daef011d2 100755 --- a/forms/HtmlEditorField.php +++ b/forms/HtmlEditorField.php @@ -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('(]* )width=([0-9]+)( [^>]*>|>)','\\1width="\\2"\\3',$content); - $content = eregi_replace('(]* )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('(]* )width=([0-9]+)( [^>]*>|>)','\\1width="\\2"\\3', $content); + $content = eregi_replace('(]* )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('/(]* )(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 = "

    $content

    "; $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', '
    |
    ' ), new TextField('getimagesSearch', _t('HtmlEditorField.SEARCHFILENAME', 'Search by file name')), diff --git a/forms/ImageField.php b/forms/ImageField.php index 4cd38968c..5c2c9e1ec 100755 --- a/forms/ImageField.php +++ b/forms/ImageField.php @@ -59,5 +59,4 @@ class ImageField extends FileField { } } - ?> \ No newline at end of file diff --git a/forms/ImageFormAction.php b/forms/ImageFormAction.php index afc3f4a5c..613c708e0 100755 --- a/forms/ImageFormAction.php +++ b/forms/ImageFormAction.php @@ -33,4 +33,4 @@ class ImageFormAction extends FormAction { } } -?> +?> \ No newline at end of file diff --git a/forms/ListboxField.php b/forms/ListboxField.php index ef4db948b..a4e332f7d 100755 --- a/forms/ListboxField.php +++ b/forms/ListboxField.php @@ -80,4 +80,4 @@ class ListboxField extends DropdownField { } } -?> +?> \ No newline at end of file diff --git a/forms/LookupField.php b/forms/LookupField.php index 55fd7ef98..dd5bd15cc 100755 --- a/forms/LookupField.php +++ b/forms/LookupField.php @@ -58,4 +58,4 @@ class LookupField extends DropdownField { } } -?> +?> \ No newline at end of file diff --git a/forms/ManyManyComplexTableField.php b/forms/ManyManyComplexTableField.php index 012d54adb..9b8b47d9c 100644 --- a/forms/ManyManyComplexTableField.php +++ b/forms/ManyManyComplexTableField.php @@ -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; - } } /** diff --git a/forms/NumericField.php b/forms/NumericField.php index 75af3da45..2eed0d4df 100755 --- a/forms/NumericField.php +++ b/forms/NumericField.php @@ -62,4 +62,4 @@ JS; return (is_numeric($this->value)) ? $this->value : 0; } } -?> +?> \ No newline at end of file diff --git a/forms/PasswordField.php b/forms/PasswordField.php index 9106b00d8..2df9000a1 100755 --- a/forms/PasswordField.php +++ b/forms/PasswordField.php @@ -49,4 +49,4 @@ class PasswordField extends FormField { } } -?> +?> \ No newline at end of file diff --git a/forms/PhoneNumberField.php b/forms/PhoneNumberField.php index 567633aeb..0326781b6 100644 --- a/forms/PhoneNumberField.php +++ b/forms/PhoneNumberField.php @@ -189,4 +189,4 @@ JS; return true; } } -?> +?> \ No newline at end of file diff --git a/forms/PopupDateTimeField.php b/forms/PopupDateTimeField.php index c5658a5d8..72de64503 100644 --- a/forms/PopupDateTimeField.php +++ b/forms/PopupDateTimeField.php @@ -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' ); diff --git a/forms/ReportField.php b/forms/ReportField.php index ca087eb98..a36fec845 100755 --- a/forms/ReportField.php +++ b/forms/ReportField.php @@ -393,5 +393,4 @@ class ReportField_Controller extends Controller { } } - ?> \ No newline at end of file diff --git a/forms/ResetFormAction.php b/forms/ResetFormAction.php index 4709ae01e..6dcf64054 100755 --- a/forms/ResetFormAction.php +++ b/forms/ResetFormAction.php @@ -8,11 +8,36 @@ class ResetFormAction extends FormAction { function Field() { - $titleAttr = $this->description ? "title=\"" . Convert::raw2att($this->description) . "\"" : ''; if($this->useButtonTag) { - return "\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 "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); } } diff --git a/forms/Tab.php b/forms/Tab.php index cd80f6e26..22154447d 100755 --- a/forms/Tab.php +++ b/forms/Tab.php @@ -56,7 +56,4 @@ class Tab extends CompositeField { } } - - - -?> +?> \ No newline at end of file diff --git a/forms/TabSet.php b/forms/TabSet.php index 0a4496980..41bedbd05 100644 --- a/forms/TabSet.php +++ b/forms/TabSet.php @@ -118,4 +118,4 @@ class TabSet extends CompositeField { parent::removeByName( $tabName, $dataFieldOnly ); } } -?> +?> \ No newline at end of file diff --git a/forms/TableField.php b/forms/TableField.php index e8383e94e..aa3854ea4 100644 --- a/forms/TableField.php +++ b/forms/TableField.php @@ -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 { } -?> +?> \ No newline at end of file diff --git a/forms/TableListField.php b/forms/TableListField.php index aa226b25f..c3a167c6b 100755 --- a/forms/TableListField.php +++ b/forms/TableListField.php @@ -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; } } -?> +?> \ No newline at end of file diff --git a/forms/TextareaField.php b/forms/TextareaField.php index c88516b50..01d1d0467 100755 --- a/forms/TextareaField.php +++ b/forms/TextareaField.php @@ -94,4 +94,4 @@ class TextareaField extends FormField { return parent::Type() . ( $this->readonly ? ' readonly' : '' ); } } -?> +?> \ No newline at end of file diff --git a/forms/TreeDropdownField.php b/forms/TreeDropdownField.php index 5bbd13659..bb0a4f743 100755 --- a/forms/TreeDropdownField.php +++ b/forms/TreeDropdownField.php @@ -137,4 +137,4 @@ HTML; } -?> +?> \ No newline at end of file diff --git a/javascript/Security_login.js b/javascript/Security_login.js index 9e44e0887..e69de29bb 100755 --- a/javascript/Security_login.js +++ b/javascript/Security_login.js @@ -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= 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; - } - } -}) \ No newline at end of file diff --git a/javascript/TableListField.js b/javascript/TableListField.js index 1c0eb2c95..8776865a8 100755 --- a/javascript/TableListField.js +++ b/javascript/TableListField.js @@ -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.
    + Behaviour.apply($(this.id), true); }.bind(this) } ); diff --git a/javascript/TreeSelectorField.js b/javascript/TreeSelectorField.js index 0c92f9467..13d5c17a0 100755 --- a/javascript/TreeSelectorField.js +++ b/javascript/TreeSelectorField.js @@ -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(); } diff --git a/main.php b/main.php index a2dc866db..3df9b3239 100644 --- a/main.php +++ b/main.php @@ -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(); diff --git a/parsers/BBCodeParser.php b/parsers/BBCodeParser.php index 18d40413d..1f7ba19a1 100644 --- a/parsers/BBCodeParser.php +++ b/parsers/BBCodeParser.php @@ -165,4 +165,4 @@ class BBCodeParser extends TextParser { } } -?> +?> \ No newline at end of file diff --git a/parsers/HTML/HTMLBBCodeParser.php b/parsers/HTML/HTMLBBCodeParser.php index 6e1bf7b6f..025ff371f 100644 --- a/parsers/HTML/HTMLBBCodeParser.php +++ b/parsers/HTML/HTMLBBCodeParser.php @@ -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 + ); + } } } diff --git a/profiler/Profiler.php b/profiler/Profiler.php index 890250514..6bbed1a3a 100644 --- a/profiler/Profiler.php +++ b/profiler/Profiler.php @@ -242,4 +242,4 @@ function profiler_stop($name) { $GLOBALS["midcom_profiler"]->stopTimer ($name); } -?> +?> \ No newline at end of file diff --git a/sake b/sake index cc3233642..4004b70ac 100755 --- a/sake +++ b/sake @@ -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 ${*} \ No newline at end of file +$php $sapphire/cli-script.php ${*} diff --git a/search/AdvancedSearchForm.php b/search/AdvancedSearchForm.php index 3cd52077b..e1696ed19 100755 --- a/search/AdvancedSearchForm.php +++ b/search/AdvancedSearchForm.php @@ -134,5 +134,4 @@ class AdvancedSearchForm extends SearchForm { } - ?> \ No newline at end of file diff --git a/search/SearchForm.php b/search/SearchForm.php index c2c33f246..bcd93de0d 100755 --- a/search/SearchForm.php +++ b/search/SearchForm.php @@ -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 { } -?> \ No newline at end of file +?> diff --git a/security/Authenticator.php b/security/Authenticator.php index e61ca898b..fc4bcfc0e 100644 --- a/security/Authenticator.php +++ b/security/Authenticator.php @@ -186,5 +186,4 @@ abstract class Authenticator extends Object { } } - ?> \ No newline at end of file diff --git a/security/ChangePasswordForm.php b/security/ChangePasswordForm.php index 67787cfb9..7c32d7e4e 100755 --- a/security/ChangePasswordForm.php +++ b/security/ChangePasswordForm.php @@ -100,5 +100,4 @@ class ChangePasswordForm extends Form { } - ?> \ No newline at end of file diff --git a/security/Group.php b/security/Group.php index 8b948943b..41027d656 100644 --- a/security/Group.php +++ b/security/Group.php @@ -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 { } } -?> \ No newline at end of file +?> diff --git a/security/LoginAttempt.php b/security/LoginAttempt.php index 82c1b5bed..cc41a2399 100644 --- a/security/LoginAttempt.php +++ b/security/LoginAttempt.php @@ -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'); diff --git a/security/Member.php b/security/Member.php index b62303bde..bb477f25a 100644 --- a/security/Member.php +++ b/security/Member.php @@ -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(); - ?> diff --git a/security/MemberAuthenticator.php b/security/MemberAuthenticator.php index a2d710ec9..b0c31a471 100644 --- a/security/MemberAuthenticator.php +++ b/security/MemberAuthenticator.php @@ -114,5 +114,4 @@ class MemberAuthenticator extends Authenticator { } } - ?> \ No newline at end of file diff --git a/security/MemberLoginForm.php b/security/MemberLoginForm.php index bdb40dc91..39ea5c5ce 100644 --- a/security/MemberLoginForm.php +++ b/security/MemberLoginForm.php @@ -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 { } } - - -?> +?> \ No newline at end of file diff --git a/security/MemberPassword.php b/security/MemberPassword.php index 47d0063ef..1063922b2 100644 --- a/security/MemberPassword.php +++ b/security/MemberPassword.php @@ -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. diff --git a/security/Permission.php b/security/Permission.php index 7bcfc7b8a..2ce7fcace 100755 --- a/security/Permission.php +++ b/security/Permission.php @@ -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 { } } - ?> diff --git a/security/Security.php b/security/Security.php index 6e0566cef..9f67f8436 100644 --- a/security/Security.php +++ b/security/Security.php @@ -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 { } - -?> \ No newline at end of file +?> diff --git a/tasks/HourlyTask.php b/tasks/HourlyTask.php index 774124248..0f9d704cd 100644 --- a/tasks/HourlyTask.php +++ b/tasks/HourlyTask.php @@ -12,4 +12,4 @@ class HourlyTask extends ScheduledTask { } -?> +?> \ No newline at end of file diff --git a/tasks/MonthlyTask.php b/tasks/MonthlyTask.php index 57e9913e8..83a89ade4 100755 --- a/tasks/MonthlyTask.php +++ b/tasks/MonthlyTask.php @@ -12,4 +12,4 @@ class MonthlyTask extends ScheduledTask { } -?> +?> \ No newline at end of file diff --git a/tasks/ScheduledTask.php b/tasks/ScheduledTask.php index 4b0b36187..8454c3fd3 100755 --- a/tasks/ScheduledTask.php +++ b/tasks/ScheduledTask.php @@ -24,4 +24,4 @@ abstract class ScheduledTask extends CliController { // this class exists as a logical extension } -?> +?> \ No newline at end of file diff --git a/templates/ComplexTableField.ss b/templates/ComplexTableField.ss index e083daedd..3d98c4934 100755 --- a/templates/ComplexTableField.ss +++ b/templates/ComplexTableField.ss @@ -64,7 +64,7 @@ <% end_control %> <% control Actions %> <% if Icon %>$Label<% else %>$Label<% end_if %> - <% end_if %> + <% end_control %> <% end_control %> <% else %> diff --git a/templates/GoogleSitemap.ss b/templates/GoogleSitemap.ss deleted file mode 100755 index 9272ed44f..000000000 --- a/templates/GoogleSitemap.ss +++ /dev/null @@ -1,11 +0,0 @@ - - - <% control Items %> - - $AbsoluteLink - $LastEdited.Format(c) - <% if ChangeFreq %>$ChangeFreq<% end_if %> - <% if Priority %>$Priority<% end_if %> - - <% end_control %> - \ No newline at end of file diff --git a/templates/RelationComplexTableField.ss b/templates/RelationComplexTableField.ss index 1d7efb834..a655a4a19 100644 --- a/templates/RelationComplexTableField.ss +++ b/templates/RelationComplexTableField.ss @@ -1,4 +1,4 @@ -
    +
    <% include TableListField_PageControls %> diff --git a/tests/ClassInfoTest.php b/tests/ClassInfoTest.php new file mode 100644 index 000000000..349858ce0 --- /dev/null +++ b/tests/ClassInfoTest.php @@ -0,0 +1,33 @@ +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 { + +} +?> \ No newline at end of file diff --git a/tests/HTTPTest.php b/tests/HTTPTest.php index 15b4fbc5f..4a67bae6e 100644 --- a/tests/HTTPTest.php +++ b/tests/HTTPTest.php @@ -7,6 +7,21 @@ */ class HTTPTest extends SapphireTest { + /** + * Tests {@link HTTP::getLinksIn()} + */ + public function testGetLinksIn() { + $content = ' +

    My page

    +

    A boy went home to see his mother.

    + '; + + $links = HTTP::getLinksIn($content); + + $this->assertTrue(is_array($links)); + $this->assertTrue(count($links) == 2); + } + /** * Tests {@link HTTP::setGetVar()} */ diff --git a/tests/api/RestfulServiceTest.php b/tests/api/RestfulServiceTest.php index 1ce14a25d..c71a92d85 100644 --- a/tests/api/RestfulServiceTest.php +++ b/tests/api/RestfulServiceTest.php @@ -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); diff --git a/tests/dev/CsvBulkLoaderTest.php b/tests/dev/CsvBulkLoaderTest.php index 71197ec55..d5c70350d 100644 --- a/tests/dev/CsvBulkLoaderTest.php +++ b/tests/dev/CsvBulkLoaderTest.php @@ -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 { ); } -?> \ No newline at end of file +?> diff --git a/tests/dev/CsvBulkLoaderTest_Players.csv b/tests/dev/CsvBulkLoaderTest_Players.csv index 2f7f3d67c..19e8923cc 100644 --- a/tests/dev/CsvBulkLoaderTest_Players.csv +++ b/tests/dev/CsvBulkLoaderTest_Players.csv @@ -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" \ No newline at end of file +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 \ No newline at end of file diff --git a/tests/dev/CsvBulkLoaderTest_PlayersWithHeader.csv b/tests/dev/CsvBulkLoaderTest_PlayersWithHeader.csv index 9f46724a2..a50c7cb47 100644 --- a/tests/dev/CsvBulkLoaderTest_PlayersWithHeader.csv +++ b/tests/dev/CsvBulkLoaderTest_PlayersWithHeader.csv @@ -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" \ No newline at end of file +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 \ No newline at end of file diff --git a/tests/fieldtypes/DateTest.php b/tests/fieldtypes/DateTest.php new file mode 100644 index 000000000..9f6612d75 --- /dev/null +++ b/tests/fieldtypes/DateTest.php @@ -0,0 +1,44 @@ + '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()); + } + } + +} +?> \ No newline at end of file diff --git a/tests/forms/DateFieldTest.php b/tests/forms/DateFieldTest.php new file mode 100644 index 000000000..161809102 --- /dev/null +++ b/tests/forms/DateFieldTest.php @@ -0,0 +1,39 @@ +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'); + } + +} +?> \ No newline at end of file diff --git a/widgets/Widget.php b/widgets/Widget.php index 0934dbc00..ee6762ef8 100644 --- a/widgets/Widget.php +++ b/widgets/Widget.php @@ -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"; diff --git a/widgets/WidgetArea.php b/widgets/WidgetArea.php index b52727712..19cf572fe 100644 --- a/widgets/WidgetArea.php +++ b/widgets/WidgetArea.php @@ -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); }