diff --git a/core/Convert.php b/core/Convert.php index ae9be210e..c9ede1f7b 100755 --- a/core/Convert.php +++ b/core/Convert.php @@ -33,7 +33,7 @@ class Convert extends Object { foreach($val as $k => $v) $val[$k] = self::raw2att($v); return $val; } else { - return str_replace(array('&','"',"'",'<','>'), array('&','"',''','<','>'), $val); + return str_replace(array('&','"',"'",'<','>'), array('&','"',''','<','>'), $val); } } @@ -71,7 +71,7 @@ class Convert extends Object { foreach($val as $k => $v) $val[$k] = self::raw2xml($v); return $val; } else { - return str_replace(array('&','<','>',"\n",'"',"'"), array('&','<','>','
','"','''), $val); + return str_replace(array('&','<','>',"\n",'"',"'"), array('&','<','>','
','"','''), $val); } } @@ -133,7 +133,7 @@ class Convert extends Object { // More complex text needs to use html2raw instead if(strpos($val,'<') !== false) return self::html2raw($val); - $converted = str_replace(array('&','<','>','"','''), array('&','<','>','"',"'"), $val); + $converted = str_replace(array('&','<','>','"',''', '''), array('&','<','>','"',"'", "'"), $val); $converted = ereg_replace('&#[0-9]+;', '', $converted); return $converted; } @@ -335,4 +335,4 @@ class Convert extends Object { } -?> \ No newline at end of file +?> diff --git a/core/Session.php b/core/Session.php index f52975197..d4049eda3 100644 --- a/core/Session.php +++ b/core/Session.php @@ -194,7 +194,9 @@ class Session { if(!session_id() && !headers_sent()) { session_set_cookie_params(self::$timeout, Director::baseURL()); - session_start(); + // @ is to supress win32 warnings/notices when session wasn't cleaned up properly + // There's nothing we can do about this, because it's an operating system function! + @session_start(); } } diff --git a/core/model/DataObject.php b/core/model/DataObject.php index c89aa5cba..910867a72 100644 --- a/core/model/DataObject.php +++ b/core/model/DataObject.php @@ -262,8 +262,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity $this->class = get_class($this); foreach($record as $k => $v) { if($v) { + if($k == 'Created' || $k == 'LastEdited') { + $fieldtype = 'SSDatetime'; + } else { + $fieldtype = $this->db($k); + } + // MSSQLDatabase::date() uses datetime for the data type for "Date" and "SSDatetime" - switch($this->db($k)) { + switch($fieldtype) { case "Date": $v = preg_replace('/:[0-9][0-9][0-9]([ap]m)$/i', ' \\1', $v); $record[$k] = date('Y-m-d', strtotime($v)); diff --git a/core/model/Hierarchy.php b/core/model/Hierarchy.php index ac79b91b3..20ff69f21 100644 --- a/core/model/Hierarchy.php +++ b/core/model/Hierarchy.php @@ -429,66 +429,18 @@ class Hierarchy extends DataObjectDecorator { $stageChildren = $this->owner->stageChildren(true); $this->_cache_allChildrenIncludingDeleted = $stageChildren; - $this->owner->extend("augmentAllChildrenIncludingDeleted", $stageChildren, $context); - - // Add live site content, if required. + // Add live site content that doesn't exist on the stage site, if required. if($this->owner->hasExtension('Versioned')) { - // Get all the requisite data, and index it - $liveChildren = $this->owner->liveChildren(true); - - if(isset($stageChildren)) { - foreach($stageChildren as $child) { - $idxStageChildren[$child->ID] = $child; - } - } - - if(isset($liveChildren)) { + // Next, go through the live children. Only some of these will be listed + $liveChildren = $this->owner->liveChildren(true, true); + if($liveChildren) { foreach($liveChildren as $child) { - $idxLiveChildren[$child->ID] = $child; - } - } - - DataObject::disable_subclass_access(); - if($idxStageChildren) { - $foundInLive = Versioned::get_by_stage( $baseClass, 'Live', "\"{$baseClass}\".\"ID\" IN (" . implode(",", array_keys($idxStageChildren)) . ")", "" ); - } - - if($idxLiveChildren) { - $foundInStage = Versioned::get_by_stage( $baseClass, 'Stage', "\"{$baseClass}\".\"ID\" IN (" . implode(",", array_keys($idxLiveChildren)) . ")", "" ); - } - DataObject::enable_subclass_access(); - - if(isset($foundInLive)) { - foreach($foundInLive as $child) { - $idxFoundInLive[$child->ID] = $child; - } - } - - if(isset($foundInStage)) { - foreach($foundInStage as $child) { - $idxFoundInStage[$child->ID] = $child; - } - } - - $this->_cache_allChildrenIncludingDeleted = new DataObjectSet(); - - // First, go through the stage children. They will all be listed but may be different colours - if($stageChildren) { - foreach($stageChildren as $child) { $this->_cache_allChildrenIncludingDeleted->push($child); } } - - // Next, go through the live children. Only some of these will be listed - if($liveChildren) { - foreach($liveChildren as $child) { - // Not found on stage = deleted page. Anything else is ignored - if(!isset($idxFoundInStage[$child->ID])) { - $this->_cache_allChildrenIncludingDeleted->push($child); - } - } - } } + + $this->owner->extend("augmentAllChildrenIncludingDeleted", $stageChildren, $context); } else { user_error("Hierarchy::AllChildren() Couldn't determine base class for '{$this->owner->class}'", E_USER_ERROR); @@ -529,7 +481,10 @@ class Hierarchy extends DataObjectDecorator { $extraFilter = $showAll ? '' : " AND \"ShowInMenus\"=1"; $baseClass = ClassInfo::baseDataClass($this->owner->class); - $staged = DataObject::get($baseClass, "\"{$baseClass}\".\"ParentID\" = " . (int)$this->owner->ID . " AND \"{$baseClass}\".\"ID\" != " . (int)$this->owner->ID . $extraFilter, ""); + $staged = DataObject::get($baseClass, "\"{$baseClass}\".\"ParentID\" = " + . (int)$this->owner->ID . " AND \"{$baseClass}\".\"ID\" != " . (int)$this->owner->ID + . $extraFilter, ""); + if(!$staged) $staged = new DataObjectSet(); $this->owner->extend("augmentStageChildren", $staged, $showAll); return $staged; @@ -538,12 +493,28 @@ class Hierarchy extends DataObjectDecorator { /** * Return children from the live site, if it exists. * @param boolean $showAll Include all of the elements, even those not shown in the menus. + * @param boolean $onlyDeletedFromStage Only return items that have been deleted from stage * @return DataObjectSet */ - public function liveChildren($showAll = false) { + public function liveChildren($showAll = false, $onlyDeletedFromStage = false) { $extraFilter = $showAll ? '' : " AND \"ShowInMenus\"=1"; + $join = ""; + $baseClass = ClassInfo::baseDataClass($this->owner->class); - return Versioned::get_by_stage($baseClass, "Live", "\"{$baseClass}\".\"ParentID\" = " . (int)$this->owner->ID . " AND \"{$baseClass}\".\"ID\" != " . (int)$this->owner->ID. $extraFilter, ""); + + if($onlyDeletedFromStage) { + // Note that the lack of double-quotes around $baseClass are the only thing preventing + // it from being rewritten to {$baseClass}_Live. This is brittle, won't work in + // postgres, and will need to be fixed *somehow*. Also, this code should probably be + // refactored to be pushed into Versioned somehow; perhaps a "doesn't exist on stage X" + // option for get_by_stage. + $join = "LEFT JOIN {$baseClass} ON {$baseClass}.\"ID\" = \"{$baseClass}\".\"ID\""; + $extraFilter .= " AND {$baseClass}.\"ID\" IS NULL"; + } + + return Versioned::get_by_stage($baseClass, "Live", "\"{$baseClass}\".\"ParentID\" = " + . (int)$this->owner->ID . " AND \"{$baseClass}\".\"ID\" != " . (int)$this->owner->ID + . $extraFilter, null, $join); } /** diff --git a/core/model/Versioned.php b/core/model/Versioned.php index 4105dfb09..bdad8d863 100755 --- a/core/model/Versioned.php +++ b/core/model/Versioned.php @@ -442,7 +442,12 @@ class Versioned extends DataObjectDecorator { $oldStage = Versioned::$reading_stage; Versioned::$reading_stage = $toStage; + + $conn = DB::getConn(); + if($conn->hasMethod('allowPrimaryKeyEditing')) $conn->allowPrimaryKeyEditing($baseClass, true); $from->write(); + if($conn->hasMethod('allowPrimaryKeyEditing')) $conn->allowPrimaryKeyEditing($baseClass, false); + $from->destroy(); Versioned::$reading_stage = $oldStage; diff --git a/core/model/fieldtypes/Date.php b/core/model/fieldtypes/Date.php index 9ccc5d2b1..4a0672d04 100644 --- a/core/model/fieldtypes/Date.php +++ b/core/model/fieldtypes/Date.php @@ -123,7 +123,7 @@ class Date extends DBField { */ function Ago() { if($this->value) { - if(time() > strtotime($this->value)) { + if(strtotime($this->value) == time() || time() > strtotime($this->value)) { return sprintf( _t( 'Date.TIMEDIFFAGO', diff --git a/core/model/fieldtypes/HTMLText.php b/core/model/fieldtypes/HTMLText.php index eb074d28c..5ba70dd6f 100755 --- a/core/model/fieldtypes/HTMLText.php +++ b/core/model/fieldtypes/HTMLText.php @@ -44,7 +44,9 @@ class HTMLText extends Text { /* Catch warnings thrown by loadHTML and turn them into a failure boolean rather than a SilverStripe error */ set_error_handler(create_function('$no, $str', 'throw new Exception("HTML Parse Error: ".$str);'), E_ALL); - try { $res = $doc->loadHTML('' . $this->value); } + // Nonbreaking spaces get converted into weird characters, so strip them + $value = str_replace(' ', ' ', $this->value); + try { $res = $doc->loadHTML('' . $value); } catch (Exception $e) { $res = false; } restore_error_handler(); diff --git a/dev/SapphireREPL.php b/dev/SapphireREPL.php index e51103a40..6bc3c67da 100644 --- a/dev/SapphireREPL.php +++ b/dev/SapphireREPL.php @@ -24,7 +24,7 @@ define('30719',E_ALL); */ class SapphireREPL extends Controller { - private function error_handler( $errno, $errstr, $errfile, $errline, $errctx ) { + public function error_handler( $errno, $errstr, $errfile, $errline, $errctx ) { // Ignore unless important error if ( ($errno & ~( 2048 | 8192 | 16384 )) == 0 ) return ; // Otherwise throw exception to handle in REPL loop diff --git a/filesystem/Folder.php b/filesystem/Folder.php index 71f62351b..f00a941cb 100755 --- a/filesystem/Folder.php +++ b/filesystem/Folder.php @@ -191,11 +191,15 @@ class Folder extends File { if($oldFile == $file && $i > 2) user_error("Couldn't fix $file with $i", E_USER_ERROR); } - if(file_exists($tmpFile['tmp_name']) && copy($tmpFile['tmp_name'], "$base/$file")) { + //$fullFilename = "$base/$file"; + $fullFilename = $base . DIRECTORY_SEPARATOR . str_replace(array("\\","/") , DIRECTORY_SEPARATOR, $file ); + + if(file_exists($tmpFile['tmp_name']) && copy($tmpFile['tmp_name'], $fullFilename)) { // Update with the new image return $this->constructChild(basename($file)); } else { - user_error("Folder::addUploadToFolder: Couldn't copy '$tmpFile[tmp_name]' to '$file'", E_USER_ERROR); + if(!file_exists($tmpFile['tmp_name'])) user_error("Folder::addUploadToFolder: '$tmpFile[tmp_name]' doesn't exist", E_USER_ERROR); + else user_error("Folder::addUploadToFolder: Couldn't copy '$tmpFile[tmp_name]' to '$fullFilename'", E_USER_ERROR); return false; } } @@ -260,7 +264,18 @@ class Folder extends File { * Returns true if this folder has children */ public function hasChildren() { - return $this->ID && $this->myChildren() && $this->myChildren()->Count() > 0; + return (bool)DB::query("SELECT COUNT(*) FROM \"File\" WHERE ParentID = " + . (int)$this->ID)->value(); + } + + /** + * Returns true if this folder has children + */ + public function hasChildFolders() { + $SQL_folderClasses = Convert::raw2sql(ClassInfo::subclassesFor('Folder')); + + return (bool)DB::query("SELECT COUNT(*) FROM \"File\" WHERE ParentID = " . (int)$this->ID + . " AND \"ClassName\" IN ('" . implode("','", $SQL_folderClasses) . "')")->value(); } /** @@ -311,7 +326,6 @@ class Folder extends File { */ function getCMSFields() { $nameField = ($this->ID > 0) ? new TextField("Name") : new HiddenField("Name"); - $fileList = new AssetTableField( $this, "Files", @@ -350,10 +364,10 @@ class Folder extends File { new LiteralField("UploadIframe", $this->getUploadIframe() ) - ), + )/*, new Tab("UnusedFiles", _t('Folder.UNUSEDFILESTAB', "Unused files"), new Folder_UnusedAssetsField($this) - ) + )*/ ), new HiddenField("ID") ); @@ -422,6 +436,12 @@ class Folder extends File { HTML; } + /** + * Get the children of this folder that are also folders. + */ + function ChildFolders() { + return DataObject::get("Folder", "ParentID = " . (int)$this->ID); + } } class Folder_UnusedAssetsField extends CompositeField { diff --git a/forms/FormAction.php b/forms/FormAction.php index d649c31db..2c36efb1c 100755 --- a/forms/FormAction.php +++ b/forms/FormAction.php @@ -19,6 +19,16 @@ class FormAction extends FormField { */ public $useButtonTag = false; + private $buttonContent = null; + + /** + * Add content inside a button field. + */ + function setButtonContent($content) { + $this->buttonContent = (string) $content; + } + + /** * Create a new action button. * @param action The method to call when the button is clicked @@ -74,7 +84,7 @@ class FormAction extends FormField { $attributes['class'] = $attributes['class'] . ' disabled'; } - return $this->createTag('button', $attributes, $this->attrTitle()); + return $this->createTag('button', $attributes, $this->buttonContent ? $this->buttonContent : $this->attrTitle()); } else { $attributes = array( 'class' => 'action' . ($this->extraClass() ? $this->extraClass() : ''), @@ -117,4 +127,4 @@ class FormAction_WithoutLabel extends FormAction { return null; } } -?> \ No newline at end of file +?>