Moved CMS module to open source path

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@39000 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Hayden Smith 2007-07-19 10:40:05 +00:00
commit abb9a61d0d
248 changed files with 18088 additions and 0 deletions

19
_config.php Normal file
View File

@ -0,0 +1,19 @@
<?php
Director::addRules(50, array(
'processes/$Action/$ID/$Batch' => 'BatchProcess_Controller',
'silverstripe/' => '->admin/',
'cms/' => '->admin/',
'admin/security/$Action/$ID/$OtherID' => 'SecurityAdmin',
'admin/help/$Action/$ID' => 'CMSHelp',
'admin/newsletter/$Action/$ID' => 'NewsletterAdmin',
'admin/reports/$Action/$ID' => 'ReportAdmin',
'admin/assets/$Action/$ID' => 'AssetAdmin',
'admin/ReportField/$Action/$ID/$Type/$OtherID' => 'ReportField_Controller',
'admin/bulkload/$Action/$ID/$OtherID' => 'BulkLoaderAdmin',
'admin/$Action/$ID/$OtherID' => 'CMSMain',
'unsubscribe/$Email/$MailingList' => 'Unsubscribe_Controller',
'membercontrolpanel/$Email' => 'MemberControlPanel'
));
?>

660
code/AssetAdmin.php Executable file
View File

@ -0,0 +1,660 @@
<?php
/**
* AssetAdmin is the 'file store' section of the CMS.
* It provides an interface for maniupating the File and Folder objects in the system.
*/
class AssetAdmin extends LeftAndMain {
static $tree_class = "File";
public function Link($action=null) {
if(!$action) $action = "index";
return "admin/assets/$action/" . $this->currentPageID();
}
/**
* Return fake-ID "root" if no ID is found (needed to upload files into the root-folder)
*/
public function currentPageID() {
if(isset($_REQUEST['ID']) && is_numeric($_REQUEST['ID'])) {
return $_REQUEST['ID'];
} elseif (is_numeric($this->urlParams['ID'])) {
return $this->urlParams['ID'];
} elseif(is_numeric(Session::get("{$this->class}.currentPage"))) {
return Session::get("{$this->class}.currentPage");
} else {
return "root";
}
}
/**
* Set up the controller, in particular, re-sync the File database with the assets folder./
*/
function init() {
parent::init();
// needed for MemberTableField (Requirements not determined before Ajax-Call)
Requirements::javascript("sapphire/javascript/ComplexTableField.js");
Requirements::css("jsparty/greybox/greybox.css");
Requirements::css("sapphire/css/ComplexTableField.css");
Requirements::javascript("cms/javascript/AssetAdmin.js");
Requirements::javascript("cms/javascript/AssetAdmin_left.js");
Requirements::javascript("cms/javascript/AssetAdmin_right.js");
// Requirements::javascript('sapphire/javascript/TableListField.js');
// Include the right JS]
// Hayden: This didn't appear to be used at all
/*$fileList = new FileList("Form_EditForm_Files", null);
$fileList->setClick_AjaxLoad('admin/assets/getfile/', 'Form_SubForm');
$fileList->FieldHolder();*/
Requirements::javascript("jsparty/greybox/AmiJS.js");
Requirements::javascript("jsparty/greybox/greybox.js");
Requirements::css("jsparty/greybox/greybox.css");
}
/**
* Display the upload form. Returns an iframe tag that will show admin/assets/uploadiframe.
*/
function getUploadIframe() {
return <<<HTML
<iframe name="AssetAdmin_upload" src="admin/assets/uploadiframe/{$this->urlParams['ID']}" id="AssetAdmin_upload" border="0" style="border-style: none; width: 100%; height: 200px">
</iframe>
HTML;
}
function index() {
File::sync();
return array();
}
/**
* Show the content of the upload iframe. The form is specified by a template.
*/
function uploadiframe() {
Requirements::clear();
Requirements::javascript("jsparty/prototype.js");
Requirements::javascript("jsparty/loader.js");
Requirements::javascript("jsparty/behaviour.js");
Requirements::javascript("jsparty/prototype_improvements.js");
Requirements::javascript("jsparty/layout_helpers.js");
Requirements::javascript("cms/javascript/LeftAndMain.js");
Requirements::javascript("jsparty/multifile/multifile.js");
Requirements::css("jsparty/multifile/multifile.css");
Requirements::css("cms/css/typography.css");
Requirements::css("cms/css/layout.css");
Requirements::css("cms/css/cms_left.css");
Requirements::css("cms/css/cms_right.css");
if(isset($data['ID']) && $data['ID'] != 'root') $folder = DataObject::get_by_id("Folder", $data['ID']);
else $folder = singleton('Folder');
$canUpload = $folder->userCanEdit();
return array( 'CanUpload' => $canUpload );
}
/**
* Return the form object shown in the uploadiframe.
*/
function UploadForm() {
return new Form($this,'UploadForm', new FieldSet(
new HiddenField("ID", "", $this->currentPageID()),
// needed because the button-action is triggered outside the iframe
new HiddenField("action_doUpload", "", "1"),
new FileField("Files[0]" , "Choose file "),
new LiteralField('UploadButton',"
<input type='submit' value='Upload Files Listed Below' name='action_upload' id='Form_UploadForm_action_upload' class='action' />
"),
new LiteralField('MultifileCode',"
<p>Files ready to upload:</p>
<div id='Form_UploadForm_FilesList'></div>
<script>
var multi_selector = new MultiSelector($('Form_UploadForm_FilesList'), null, $('Form_UploadForm_action_upload'));
multi_selector.addElement($('Form_UploadForm_Files-0'));
</script>
")
), new FieldSet(
));
}
/**
* This method processes the results of the UploadForm.
* It will save the uploaded files to /assets/ and create new File objects as required.
*/
function doUpload($data, $form) {
foreach($data['Files'] as $param => $files) {
foreach($files as $key => $value) {
$processedFiles[$key][$param] = $value;
}
}
if($data['ID'] && $data['ID'] != 'root') $folder = DataObject::get_by_id("Folder", $data['ID']);
else $folder = singleton('Folder');
$warnFiles = array();
$fileSizeWarnings = '';
foreach($processedFiles as $file) {
if($file['tmp_name']) {
// check that the file can be uploaded and isn't too large
$extensionIndex = strripos( $file['name'], '.' );
$extension = strtolower( substr( $file['name'], $extensionIndex + 1 ) );
if( $extensionIndex !== FALSE )
list( $maxSize, $warnSize ) = File::getMaxFileSize( $extension );
else
list( $maxSize, $warnSize ) = File::getMaxFileSize();
// check that the file is not too large or that the current user is an administrator
if( $this->can('AdminCMS') || ( File::allowedFileType( $extension ) && (!isset($maxsize) || $file['size'] < $maxSize)))
$newFiles[] = $folder->addUploadToFolder($file);
elseif( !File::allowedFileType( $extension ) ) {
$fileSizeWarnings .= "alert( 'Only administrators can upload $extension files.' );";
} else {
if( $file['size'] > 1048576 )
$fileSize = "" . ceil( $file['size'] / 1048576 ) . "MB";
elseif( $file['size'] > 1024 )
$fileSize = "" . ceil( $file['size'] / 1024 ) . "KB";
else
$fileSize = "" . ceil( $file['size'] ) . "B";
$fileSizeWarnings .= "alert( '\\'" . $file['name'] . "\\' is too large ($fileSize). Files of this type cannot be larger than $warnSize ' );";
}
}
}
if($newFiles) {
$numFiles = sizeof($newFiles);
$statusMessage = "Uploaded $numFiles files";
$status = "good";
} else {
$statusMessage = "There was nothing to upload";
$status = "";
}
echo <<<HTML
<script type="text/javascript">
var form = parent.document.getElementById('Form_EditForm');
form.getPageFromServer(form.elements.ID.value);
parent.statusMessage("{$statusMessage}","{$status}");
$fileSizeWarnings
parent.document.getElementById('sitetree').getTreeNodeByIdx( "{$folder->ID}" ).getElementsByTagName('a')[0].className += ' contents';
</script>
HTML;
}
/**
* Needs to be overridden to make sure an ID with value "0" is still valid (rootfolder)
*/
/**
* Return the form that displays the details of a folder, including a file list and fields for editing the folder name.
*/
function getEditForm($id) {
if($id && $id != "root") {
$record = DataObject::get_by_id("File", $id);
} else {
$record = singleton("Folder");
}
$fileList = new AssetTableField(
$this,
"Files",
"File",
array("Title" => "Title", "LinkedURL" => "Filename"),
""
);
$fileList->setFolder($record);
$fileList->setPopupCaption("View/Edit Asset");
if($record) {
$nameField = ($id != "root") ? new TextField("Name") : new HiddenField("Name");
$fields = new FieldSet(
new HiddenField("Title"),
$nameField,
new TabSet("Root",
new Tab("Files",
$fileList
),
new Tab("Details",
new ReadonlyField("URL"),
new ReadonlyField("ClassName", "Type"),
new ReadonlyField("Created", "First Uploaded"),
new ReadonlyField("LastEdited", "Last Updated")
),
new Tab("Upload",
new LiteralField("UploadIframe",
$this->getUploadIframe()
)
)
),
new HiddenField("ID")
);
$actions = new FieldSet();
if( $record->userCanEdit() ) {
$actions = new FieldSet(
new FormAction('deletemarked',"Delete files"),
new FormAction('movemarked',"Move files..."),
new FormAction('save',"Save")
);
}
$form = new Form($this, "EditForm", $fields, $actions);
if($record->ID) {
$form->loadDataFrom($record);
} else {
$form->loadDataFrom(array(
"ID" => "root",
"URL" => Director::absoluteBaseURL() . 'assets/',
));
}
// @todo: These workflow features aren't really appropriate for all projects
if( Member::currentUser()->_isAdmin() && project() == 'mot' ) {
$fields->addFieldsToTab( 'Root.Workflow', new DropdownField("Owner", "Owner", Member::map() ) );
$fields->addFieldsToTab( 'Root.Workflow', new TreeMultiselectField("CanUse", "Content usable by") );
$fields->addFieldsToTab( 'Root.Workflow', new TreeMultiselectField("CanEdit", "Content modifiable by") );
}
if( !$record->userCanEdit() )
$form->makeReadonly();
return $form;
}
}
/**
* Returns the form used to specify options for the "move marked" action.
*/
public function MoveMarkedOptionsForm() {
$folderDropdown = new TreeDropdownField("DestFolderID", "Move files to", "Folder");
$folderDropdown->setFilterFunction(create_function('$obj', 'return $obj->class == "Folder";'));
return new CMSActionOptionsForm($this, "MoveMarkedOptionsForm", new FieldSet(
new HiddenField("ID"),
new HiddenField("FileIDs"),
$folderDropdown
),
new FieldSet(
new FormAction("movemarked", "Move marked files")
));
}
/**
* Perform the "move marked" action.
* Called by ajax, with a JavaScript return.
*/
public function movemarked() {
if($_REQUEST['DestFolderID'] && is_numeric($_REQUEST['DestFolderID'])) {
$destFolderID = $_REQUEST['DestFolderID'];
$fileList = "'" . ereg_replace(' *, *',"','",trim(addslashes($_REQUEST['FileIDs']))) . "'";
$numFiles = 0;
if($fileList != "''") {
$files = DataObject::get("File", "`File`.ID IN ($fileList)");
if($files) {
foreach($files as $file) {
$file->ParentID = $destFolderID;
$file->write();
$numFiles++;
}
} else {
user_error("No files in $fileList could be found!", E_USER_ERROR);
}
}
echo <<<JS
statusMessage("Moved $numFiles files");
JS;
} else {
user_error("Bad data: $_REQUEST[DestFolderID]", E_USER_ERROR);
}
}
/**
* Returns the form used to specify options for the "delete marked" action.
* In actual fact, this form only has hidden fields and the button is auto-clickd without the
* form being displayed; it's just the most consistent way of providing this information to the
* CMS.
*/
public function DeleteMarkedOptionsForm() {
return new CMSActionOptionsForm($this, "DeleteMarkedOptionsForm", new FieldSet(
new HiddenField("ID"),
new HiddenField("FileIDs")
),
new FieldSet(
new FormAction("deletemarked", "Delete marked files")
));
}
/**
* Perform the "delete marked" action.
* Called by ajax, with a JavaScript return.
*/
public function deletemarked() {
$fileList = "'" . ereg_replace(' *, *',"','",trim(addslashes($_REQUEST['FileIDs']))) . "'";
$numFiles = 0;
$folderID = 0;
$deleteList = '';
$brokenPageList = '';
if($fileList != "''") {
$files = DataObject::get("File", "`File`.ID IN ($fileList)");
if($files) {
foreach($files as $file) {
if( !$folderID )
$folderID = $file->ParentID;
// $deleteList .= "\$('Form_EditForm_Files').removeById($file->ID);\n";
$file->delete();
$numFiles++;
}
if($brokenPages = Notifications::getItems("BrokenLink")) {
$brokenPageList = " These pages now have broken links:</ul>";
foreach($brokenPages as $brokenPage) {
$brokenPageList .= "<li style=&quot;font-size: 65%&quot;>" . $brokenPage->Breadcrumbs(3, true) . "</li>";
}
$brokenPageList .= "</ul>";
Notifications::notifyByEmail("BrokenLink", "Page_BrokenLinkEmail");
}
if( $folderID ) {
$remaining = DB::query("SELECT COUNT(*) FROM `File` WHERE `ParentID`=$folderID")->value();
if( !$remaining )
$deleteList = "Element.removeClassName(\$('sitetree').getTreeNodeByIdx( '$folderID' ).getElementsByTagName('a')[0],'contents');";
}
} else {
user_error("No files in $fileList could be found!", E_USER_ERROR);
}
}
echo <<<JS
$deleteList
$('Form_EditForm').getPageFromServer($('Form_EditForm_ID').value);
statusMessage("Deleted $numFiles files.$brokenPageList");
JS;
}
/**
* Returns the content to be placed in Form_SubForm when editing a file.
* Called using ajax.
*/
public function getfile() {
SSViewer::setOption('rewriteHashlinks', false);
// bdc: only try to return something if user clicked on an object
if (is_object($this->getSubForm($this->urlParams['ID']))) {
return $this->getSubForm($this->urlParams['ID'])->formHtmlContent();
}
else return null;
}
/**
* Action handler for the save button on the file subform.
* Saves the file
*/
public function savefile($data, $form) {
$record = DataObject::get_by_id("File", $data['ID']);
$form->saveInto($record);
$record->write();
$title = Convert::raw2js($record->Title);
$name = Convert::raw2js($record->Name);
echo <<<JS
statusMessage('Saved file #$data[ID]');
$('record-$data[ID]').getElementsByTagName('td')[1].innerHTML = "$title";
$('record-$data[ID]').getElementsByTagName('td')[2].innerHTML = "$name";
JS;
}
/**
* Return the entire site tree as a nested set of ULs
*/
public function SiteTreeAsUL() {
$obj = singleton('Folder');
$obj->setMarkingFilter("ClassName", "Folder");
$obj->markPartialTree();
if($p = $this->currentPage()) $obj->markToExpose($p);
// getChildrenAsUL is a flexible and complex way of traversing the tree
$siteTree = $obj->getChildrenAsUL("",
' "<li id=\"record-$child->ID\" class=\"$child->class" . $child->markingClasses() . ($extraArg->isCurrentPage($child) ? " current" : "") . "\">" . ' .
' "<a href=\"" . Director::link(substr($extraArg->Link(),0,-1), "show", $child->ID) . "\" class=\"" . ($child->hasChildren() ? " contents" : "") . "\" >" . $child->Title . "</a>" ',
$this, true);
// Wrap the root if needs be.
$rootLink = $this->Link() . 'show/root';
if(!isset($rootID)) $siteTree = "<ul id=\"sitetree\" class=\"tree unformatted\"><li id=\"record-root\" class=\"Root\"><a href=\"$rootLink\">http://www.yoursite.com/assets</a>"
. $siteTree . "</li></ul>";
return $siteTree;
}
/**
* Returns a subtree of items underneat the given folder.
*/
public function getsubtree() {
$obj = DataObject::get_by_id("Folder", $_REQUEST['ID']);
$obj->setMarkingFilter("ClassName", "Folder");
$obj->markPartialTree();
$results = $obj->getChildrenAsUL("",
' "<li id=\"record-$child->ID\" class=\"$child->class" . $child->markingClasses() . ($extraArg->isCurrentPage($child) ? " current" : "") . "\">" . ' .
' "<a href=\"" . Director::link(substr($extraArg->Link(),0,-1), "show", $child->ID) . "\" >" . $child->Title . "</a>" ',
$this, true);
return substr(trim($results), 4,-5);
}
//------------------------------------------------------------------------------------------//
// Data saving handlers
/**
* Add a new folder and return its details suitable for ajax.
*/
public function addfolder() {
$parent = ($_REQUEST['ParentID'] && is_numeric($_REQUEST['ParentID'])) ? $_REQUEST['ParentID'] : 0;
if($parent) {
$parentObj = DataObject::get_by_id("File", $parent);
if(!$parentObj || !$parentObj->ID) $parent = 0;
}
$p = new Folder();
$p->ParentID = $parent;
$p->Title = "NewFolder";
$p->Name = "NewFolder";
// Get the folder to be created
if(isset($parentObj->ID)) $filename = $parentObj->FullPath . $p->Name;
else $filename = '../assets/' . $p->Name;
// Ensure uniqueness
$i = 2;
$baseFilename = $filename . '-';
while(file_exists($filename)) {
$filename = $baseFilename . $i;
$p->Name = $p->Title = basename($filename);
$i++;
}
// Actually create
mkdir($filename);
chmod($filename, 02775);
$p->write();
return $this->returnItemToUser($p);
}
/**
* Return the given tree item to the client.
* If called by ajax, this will be some javascript commands.
* Otherwise, it will redirect back.
*/
public function returnItemToUser($p) {
if($_REQUEST['ajax']) {
$parentID = (int)$p->ParentID;
return <<<JS
tree = $('sitetree');
var newNode = tree.createTreeNode($p->ID, "$p->Title", "$p->class");
tree.getTreeNodeByIdx($parentID).appendTreeNode(newNode);
newNode.selectTreeNode();
JS;
} else {
Director::redirectBack();
}
}
/**
* Delete a folder
*/
public function deletefolder() {
$script = '';
$ids = split(' *, *', $_REQUEST['csvIDs']);
foreach($ids as $id) {
if(is_numeric($id)) {
$record = DataObject::get_by_id($this->stat('tree_class'), $id);
if(!$record)
Debug::message( "Record appears to be null" );
/*if($record->hasMethod('BackLinkTracking')) {
$brokenPages = $record->BackLinkTracking();
foreach($brokenPages as $brokenPage) {
$brokenPageList .= "<li style=\"font-size: 65%\">" . $brokenPage->Breadcrumbs(3, true) . "</li>";
$brokenPage->HasBrokenLink = true;
$notifications[$brokenPage->OwnerID][] = $brokenPage;
$brokenPage->write();
}
}*/
$record->delete();
$record->destroy();
// DataObject::delete_by_id($this->stat('tree_class'), $id);
$script .= $this->deleteTreeNodeJS($record);
}
}
/*if($notifications) foreach($notifications as $memberID => $pages) {
$email = new Page_BrokenLinkEmail();
$email->populateTemplate(new ArrayData(array(
"Recipient" => DataObject::get_by_id("Member", $memberID),
"BrokenPages" => new DataObjectSet($pages),
)));
$email->debug();
$email->send();
}*/
$s = (sizeof($ids) > 1) ? "s" :"";
$message = sizeof($ids) . " folder$s deleted.";
//
if(isset($brokenPageList)) $message .= " The following pages now have broken links:<ul>" . addslashes($brokenPageList) . "</ul>Their owners have been emailed and they will fix up those pages.";
$script .= "statusMessage('$message');";
echo $script;
}
public function removefile(){
if($fileID = $this->urlParams['ID']){
$file = DataObject::get_by_id('File', $fileID);
$file->delete();
$file->destroy();
if(Director::is_ajax()) {
echo <<<JS
$('Form_EditForm_Files').removeFile($fileID);
statusMessage('removed file', 'good');
JS;
}else{
Director::redirectBack();
}
}else{
user_error("AssetAdmin::removefile: Bad parameters: File=$fileID", E_USER_ERROR);
}
}
public function save($urlParams, $form) {
// Don't save the root folder - there's no database record
if($_REQUEST['ID'] == 'root') {
FormResponse::status_message("Saved", "good");
return FormResponse::respond();
}
$form->dataFieldByName('Title')->value = $form->dataFieldByName('Name')->value;
return parent::save($urlParams, $form);
}
}

130
code/AssetTableField.php Executable file
View File

@ -0,0 +1,130 @@
<?php
class AssetTableField extends ComplexTableField {
protected $folder;
protected $template = "AssetTableField";
function __construct($controller, $name, $sourceClass, $fieldList, $detailFormFields, $sourceFilter = "", $sourceSort = "", $sourceJoin = "") {
parent::__construct($controller, $name, $sourceClass, $fieldList, $detailFormFields, $sourceFilter, $sourceSort, $sourceJoin);
$this->sourceSort = "Title";
$this->Markable = true;
}
function setFolder($folder) {
$this->folder = $folder;
$this->sourceFilter .= ($this->sourceFilter) ? " AND " : "";
$this->sourceFilter .= " ParentID = '" . $folder->ID . "' AND ClassName <> 'Folder'";
}
function Folder() {
return $this->folder;
}
function sourceID() {
return $this->folder->ID;
}
function DetailForm() {
$ID = (isset($_REQUEST['ctf']['ID'])) ? Convert::raw2xml($_REQUEST['ctf']['ID']) : null;
$childID = (isset($_REQUEST['ctf']['childID'])) ? Convert::raw2xml($_REQUEST['ctf']['childID']) : null;
$childClass = (isset($_REQUEST['fieldName'])) ? Convert::raw2xml($_REQUEST['fieldName']) : null;
$methodName = (isset($_REQUEST['methodName'])) ? $_REQUEST['methodName'] : null;
if(!$childID) {
user_error("AssetTableField::DetailForm Please specify a valid ID");
return null;
}
if($childID) {
$childData = DataObject::get_by_id("File", $childID);
}
if(!$childData) {
user_error("AssetTableField::DetailForm No record found");
return null;
}
if($childData->ParentID) {
$folder = DataObject::get_by_id('File', $childData->ParentID );
} else {
$folder = singleton('Folder');
}
$urlLink = "<div class='field readonly'>";
$urlLink .= "<label class='left'>URL</label>";
$urlLink .= "<span class='readonly'><a href='{$childData->Link()}'>{$childData->RelativeLink()}</a></span>";
$urlLink .= "</div>";
$detailFormFields = new FieldSet(
new TabSet("BottomRoot",
new Tab("Main",
new TextField("Title"),
new TextField("Name", "Filename"),
new LiteralField("AbsoluteURL", $urlLink),
new ReadonlyField("FileType", "Type"),
new ReadonlyField("Size", "Size", $childData->getSize()),
new DropdownField("OwnerID", "Owner", Member::mapInCMSGroups( $folder->CanEdit() ) ),
new DateField_Disabled("Created", "First uploaded"),
new DateField_Disabled("LastEdited", "Last changed")
)
)
);
if(is_a($childData,'Image')) {
$big = $childData->URL;
$thumbnail = $childData->getFormattedImage('AssetLibraryPreview')->URL;
$detailFormFields->addFieldToTab("BottomRoot.Main",
new ReadonlyField("Dimensions"),
"Created"
);
$detailFormFields->addFieldToTab("BottomRoot",
new Tab("Image",
new LiteralField("ImageFull",
"<img src='{$thumbnail}' alt='{$childData->Name}' />"
)
),
'Main'
);
}
if($childData && $childData->hasMethod('BackLinkTracking')) {
$links = $childData->BackLinkTracking();
if($links->exists()) {
foreach($links as $link) {
$backlinks[] = "<li><a href=\"admin/show/$link->ID\">" . $link->Breadcrumbs(null,true). "</a></li>";
}
$backlinks = "<div style=\"clear:left\">The following pages link to this file:<ul>" . implode("",$backlinks) . "</ul>";
}
if(!isset($backlinks)) $backlinks = "<p>This file hasn't been linked to from any pages.</p>";
$detailFormFields->addFieldToTab("BottomRoot.Links", new LiteralField("Backlinks", $backlinks));
}
// the ID field confuses the Controller-logic in finding the right view for ReferencedField
$detailFormFields->removeByName('ID');
// add a namespaced ID instead thats "converted" by saveComplexTableField()
$detailFormFields->push(new HiddenField("ctf[childID]","",$childID));
$detailFormFields->push(new HiddenField("ctf[ClassName]","",$this->sourceClass));
$readonly = ($this->methodName == "show");
$form = new ComplexTableField_Popup($this, "DetailForm", $detailFormFields, $this->sourceClass, $readonly);
if (is_numeric($childID)) {
if ($methodName == "show" || $methodName == "edit") {
$form->loadDataFrom($childData);
}
}
if( !$folder->userCanEdit() || $methodName == "show") {
$form->makeReadonly();
}
return $form;
}
}
?>

67
code/BulkLoader.php Executable file
View File

@ -0,0 +1,67 @@
<?php
abstract class BulkLoader extends ViewableData {
/**
* Override this on subclasses to give the specific functions names
*/
static $title = null;
/**
* Return a human-readable name for this object.
* It defaults to the class name can be overridden by setting the static variable $title
*/
function Title() {
if($title = $this->stat('title')) return $title;
else return $this->class;
}
/**
* Process every record in the file
* @param filename The name of the CSV file to process
* @param preview If true, we'll just output a summary of changes but not actually do anything
*
* @returns A DataObjectSet containing a list of all the reuslst
*/
function processAll($filename, $preview = false) {
// TODO
// Get the first record out of the CSV and store it as headers
// Get each record out of the CSV
// Remap the record so that it's keyed by headers
// Pass it to $this->processRecord, and get the results
// Put the results inside an ArrayData and push that onto a DataObjectSet for returning
}
/*----------------------------------------------------------------------------------------
* Next, we have some abstract functions that let subclasses say what kind of batch operation they're
* going to do
*----------------------------------------------------------------------------------------
*/
/**
* Return a FieldSet containing all the options for this form; this
* doesn't include the actual upload field itself
*/
abstract function getOptionFields();
/**
* Process a single record from the CSV file.
* @param record An map of the CSV data, keyed by the header field
* @param preview
*
* @returns A 2 value array.
* - The first element should be "add", "edit" or "", depending on the operation performed in response to this record
* - The second element is a free-text string that can optionally provide some more information about what changes have
* been made
*/
abstract function processRecord($record, $preview = false);
/*----------------------------------------------------------------------------------------
* Next, we have a library of helper functions (Brian to build as necessary)
*----------------------------------------------------------------------------------------
*/
}
?>

123
code/BulkLoaderAdmin.php Executable file
View File

@ -0,0 +1,123 @@
<?php
/**
* Class to provide batch-update facilities to CMS users.
* The BulkLoaderAdmin class provides an interface for accessing all of the subclasses of BulkLoader,
* each of which defines a particular bulk loading operation.
*
* This code was originally developed for Per Week in collaboration with Brian Calhoun.
*/
class BulkLoaderAdmin extends LeftAndMain {
/**
* Initialisation method called before accessing any functionality that BulkLoaderAdmin has to offer
*/
public function init() {
Requirements::javascript('cms/javascript/BulkLoaderAdmin.js');
parent::init();
}
/**
* Link function to tell us how to get back to this controller.
*/
public function Link($action = null) {
return "admin/bulkload/$action";
}
public function BulkLoaders() {
$items = ClassInfo::subclassesFor("BulkLoader");
array_shift($items);
foreach($items as $item) {
$itemObjects[] = new $item();
}
return new DataObjectSet($itemObjects);
}
/**
* Return the form shown when we first click on a loader on the left.
* Provides all the options, a file upload, and an option to proceed
*/
public function getEditForm($className = null) {
if(is_subclass_of($className, 'BulkLoader')) {
$loader = new $className();
$fields = $loader->getOptionFields();
if(!$fields) $fields = new FieldSet();
$fields->push(new FileField("File", "CSV File"));
$fields->push(new HiddenField('LoaderClass', '', $loader->class));
return new Form($this, "EditForm",
$fields,
new FieldSet(
new FormAction('preview', "Preview")
)
);
}
}
public function preview() {
$className = $_REQUEST['LoaderClass'];
if(is_subclass_of($className, 'BulkLoader')) {
$loader = new $className();
$results = $loader->processAll($_FILES['File']['tmp_name'], false);
return $this->customise(array(
"Message" => "Press continue to load this data in",
"Results" => $results,
"ConfirmForm" => $this->getConfirmFormFor($loader, $file),
))->renderWith("BulkLoaderAdmin_preview");
}
}
/**
* Generate a confirmation form for the given file/class
* Will copy the file to a suitable temporary location
* @param loader A BulkLoader object
* @param file The name of the temp file
*/
public function getConfirmFormFor($loader, $file) {
$tmpFile = tempnam(TEMP_FOLDER,'batch-upload-');
copy($file,$tmpFile);
return new Form($this, "ConfirmForm", new FieldSet(
new HiddenField("File", "", $tmpFile),
new HiddenField("LoaderClass", "", $loader->class)
), new FieldSet(
new FormAction('process', 'Confirm bulk load')
));
}
/**
* Stub to return the form back after pressing the button.
*/
public function ConfirmForm() {
$className = $_REQUEST['LoaderClass'];
return $this->getConfirmFormFor(new $className(), $_REQUEST['File']);
}
/**
* Process the data and display the final "finished" message
*/
public function process() {
$className = $_REQUEST['LoaderClass'];
if(is_subclass_of($className, 'BulkLoader')) {
$loader = new $className();
$results = $loader->processAll($_REQUEST['Filename'], true);
return $this->customise(array(
"Message" => "This data has been loaded in",
"Results" => $results,
"ConfirmForm" => " ",
))->renderWith("BulkLoaderAdmin_preview");
}
}
}
?>

17
code/CMSActionOptionsForm.php Executable file
View File

@ -0,0 +1,17 @@
<?php
/**
* A special kind of form used to make the action dialogs that appear just underneath the top-right
* buttons in the CMS
*/
class CMSActionOptionsForm extends Form {
function FormAttributes() {
return "class=\"actionparams\" style=\"display:none\" " . parent::FormAttributes();
}
function FormName() {
$action = $this->actions->First()->Name();
return "{$action}_options";
}
}
?>

8
code/CMSHelp.php Executable file
View File

@ -0,0 +1,8 @@
<?php
class CMSHelp extends Controller {
}
?>

1132
code/CMSMain.php Normal file
View File

@ -0,0 +1,1132 @@
<?php
/**
* The main "content" area of the CMS.
* This class creates a 2-frame layout - left-tree and right-form - to sit beneath the main
* admin menu.
* @todo Create some base classes to contain the generic functionality that will be replicated.
*/
class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionProvider {
static $tree_class = "SiteTree";
static $subitem_class = "Member";
public function init() {
parent::init();
Requirements::javascript(MCE_ROOT . "tiny_mce_src.js");
Requirements::javascript("jsparty/tiny_mce_improvements.js");
Requirements::javascript("jsparty/hover.js");
Requirements::javascript("jsparty/scriptaculous/controls.js");
Requirements::javascript("cms/javascript/MemberList.js");
Requirements::javascript("cms/javascript/CMSMain.js");
Requirements::javascript("cms/javascript/CMSMain_left.js");
Requirements::javascript("cms/javascript/CMSMain_right.js");
Requirements::javascript("cms/javascript/ForumAdmin.js");
Requirements::javascript("cms/javascript/SideTabs.js");
Requirements::javascript("cms/javascript/TaskList.js");
Requirements::javascript("cms/javascript/CommentList.js");
Requirements::javascript("cms/javascript/SideReports.js");
Requirements::javascript("sapphire/javascript/UpdateURL.js");
Requirements::javascript("sapphire/javascript/UniqueFields.js");
Requirements::javascript("sapphire/javascript/RedirectorPage.js");
Requirements::css("sapphire/css/FieldEditor.css");
Requirements::javascript("sapphire/javascript/FieldEditor.js");
Requirements::javascript("sapphire/javascript/TableListField.js");
Requirements::javascript("sapphire/javascript/TableField.js");
Requirements::javascript("sapphire/javascript/ComplexTableField.js");
Requirements::javascript("survey/javascript/SurveyResponses.js");
Requirements::javascript("survey/javascript/FormResponses.js");
Requirements::javascript( 'sapphire/javascript/DataReport.js' );
Requirements::css("survey/css/SurveyFilter.css");
Requirements::javascript("sapphire/javascript/SelectionGroup.js");
// For Parents
Requirements::javascript("parents/javascript/NotifyMembers.js");
// For Tourism
Requirements::css("tourism/css/SurveyCMSMain.css");
Requirements::javascript("tourism/javascript/QuotasReport.js");
// For Mikano
Requirements::javascript("sapphire/javascript/ReportField.js");
// For Ptraining
Requirements::javascript("ptraining/javascript/BookingList.js");
// For Forum
Requirements::javascript("forum/javascript/ForumAccess.js");
// For Gallery
Requirements::javascript('gallery/javascript/GalleryPage_CMS.js');
Requirements::css(project() . "/css/typography.css");
// HACK ALERT:
// We need a better way of including all of the CSS that *might* be used by this application.
// Perhaps the ajax responses can include some instructions to go get more CSS / JavaScript?
if(Director::fileExists('mot')) {
Requirements::css("mot/css/WorkflowWidget.css");
}
// We don't want this showing up in every ajax-response, it should always be present in a CMS-environment
if(!Director::is_ajax()) {
Requirements::javascriptTemplate("cms/javascript/tinymce.template.js", array(
"ContentCSS" => project() . "/css/editor.css",
"BaseURL" => Director::absoluteBaseURL(),
));
}
}
//------------------------------------------------------------------------------------------//
// Main controllers
//------------------------------------------------------------------------------------------//
// Main UI components
/**
* Return the entire site tree as a nested set of ULs
*/
public function SiteTreeAsUL() {
$this->generateDataTreeHints();
$this->generateTreeStylingJS();
return $this->getSiteTreeFor("SiteTree");
}
public function generateDataTreeHints() {
$classes = ClassInfo::subclassesFor( $this->stat('tree_class') );
$def['Root'] = array();
foreach($classes as $class) {
$obj = singleton($class);
if($obj instanceof HiddenClass) continue;
$allowedChildren = $obj->allowedChildren();
if($allowedChildren != "none") $def[$class]['allowedChildren'] = $allowedChildren;
$def[$class]['defaultChild'] = $obj->defaultChild();
$def[$class]['defaultParent'] = isset(DataObject::get_by_url($obj->defaultParent())->ID) ? DataObject::get_by_url($obj->defaultParent())->ID : null;
if(is_array($allowedChildren)) foreach($allowedChildren as $allowedChild) {
$def[$allowedChild]['allowedParents'][] = $class;
}
if($obj->stat('can_be_root')) {
$def['Root']['allowedChildren'][] = $class;
}
}
// Put data hints into a script tag at the top
Requirements::customScript("siteTreeHints = " . $this->jsDeclaration($def) . ";");
}
public function generateTreeStylingJS() {
$classes = ClassInfo::subclassesFor('SiteTree');
foreach($classes as $class) {
$obj = singleton($class);
if($obj instanceof HiddenClass) continue;
if($icon = $obj->stat('icon')) $iconInfo[$class] = $icon;
}
$iconInfo['BrokenLink'] = 'cms/images/treeicons/brokenlink';
$js = "var _TREE_ICONS = [];\n";
foreach($iconInfo as $class => $icon) {
// SiteTree::$icon can be set to array($icon, $option)
// $option can be "file" or "folder" to force the icon to always be the file or the folder form
$option = null;
if(is_array($icon)) list($icon, $option) = $icon;
$fileImage = ($option == "folder") ? $icon . '-openfolder.gif' : $icon . '-file.gif';
$openFolderImage = $icon . '-openfolder.gif';
if(!Director::fileExists($openFolderImage) || $option = "file") $openFolderImage = $fileImage;
$closedFolderImage = $icon . '-closedfolder.gif';
if(!Director::fileExists($closedFolderImage) || $option = "file") $closedFolderImage = $fileImage;
$js .= <<<JS
_TREE_ICONS['$class'] = {
fileIcon: '$fileImage',
openFolderIcon: '$openFolderImage',
closedFolderIcon: '$closedFolderImage'
};
JS;
}
Requirements::customScript($js);
}
/**
* Return a javascript instanciation of this array
*/
protected function jsDeclaration($array) {
if(is_array($array)) {
$object = false;
foreach(array_keys($array) as $key) {
if(!is_numeric($key)) {
$object = true;
break;
}
}
if($object) {
foreach($array as $k => $v) {
$parts[] = "$k : " . $this->jsDeclaration($v);
}
return " {\n " . implode(", \n", $parts) . " }\n";
} else {
foreach($array as $part) $parts[] = $this->jsDeclaration($part);
return " [ " . implode(", ", $parts) . " ]\n";
}
} else {
return "'" . addslashes($array) . "'";
}
}
/**
* Populates an array of classes in the CMS which allows the
* user to change the page type.
*/
public function PageTypes() {
$classes = ClassInfo::getValidSubClasses();
array_shift($classes);
$result = new DataObjectSet();
$kill_ancestors[] = null;
// 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) {
foreach ($kill_ancestors as $mark) {
// unset from $classes
unset($classes[$mark]);
}
}
foreach($classes as $class) {
$instance = singleton($class);
if($instance instanceof HiddenClass) continue;
if( !$instance->canCreate() ) continue;
// skip this type if it is restricted
if($instance->stat('need_permission') && !$this->can( singleton($class)->stat('need_permission') ) ) continue;
$addAction = $instance->uninherited('add_action', true);
if(!$addAction) $addAction = "a $class";
$result->push(new ArrayData(array(
"ClassName" => $class,
"AddAction" => "Create $addAction",
)));
}
return $result;
}
/**
* Get a databsae record to be managed by the CMS
*/
public function getRecord($id) {
$treeClass = $this->stat('tree_class');
if($id && is_numeric($id)) {
$record = DataObject::get_one( $treeClass, "`$treeClass`.ID = $id");
if(!$record) {
// $record = Versioned::get_one_by_stage($treeClass, "Live", "`$treeClass`.ID = $id");
Versioned::reading_stage('Live');
singleton($treeClass)->flushCache();
$record = DataObject::get_one( $treeClass, "`$treeClass`.ID = $id");
if($record) {
$record->DeletedFromStage = true;
} else {
Versioned::reading_stage(null);
}
}
return $record;
} else if(substr($id,0,3) == 'new') {
return $this->getNewItem($id);
}
}
public function getEditForm($id) {
$record = $this->getRecord($id);
if($record) {
if($record->DeletedFromStage) $record->Status = "Removed from the draft site";
$fields = $record->getCMSFields($this);
$fields->push($idField = new HiddenField("ID"));
$fields->push($liveURLField = new HiddenField("LiveURLSegment"));
$fields->push($stageURLField = new HiddenField("StageURLSegment"));
/*if( substr($record->ID, 0, 3 ) == 'new' )*/
$fields->push(new HiddenField('Sort','', $record->Sort ));
$idField->setValue($id);
if($record->ID && is_numeric( $record->ID ) ) $liveURLField->setValue(DB::query("SELECT URLSegment FROM SiteTree_Live WHERE ID = $id")->value());
if(!$record->DeletedFromStage) $stageURLField->setValue($record->URLSegment);
// getAllCMSActions can be used to completely redefine the action list
if($record->hasMethod('getAllCMSActions')) {
$actions = $record->getAllCMSActions();
} else {
$actions = new FieldSet();
if($record->DeletedFromStage) {
if($record->can('CMSEdit')) {
$actions->push(new FormAction('revert','Restore'));
$actions->push(new FormAction('deletefromlive','Delete from the published site'));
}
} else {
if($record->hasMethod('getCMSActions')) {
$extraActions = $record->getCMSActions();
if($extraActions) foreach($extraActions as $action) $actions->push($action);
}
if($record->canEdit()) {
$actions->push(new FormAction('save','Save draft'));
}
}
}
$form = new Form($this, "EditForm", $fields, $actions);
$form->loadDataFrom($record);
$form->disableDefaultAction();
if(!$record->canEdit() || $record->DeletedFromStage) $form->makeReadonly();
return $form;
} else if($id) {
return new Form($this, "EditForm", new FieldSet(
new LabelField("This page doesn't exist")), new FieldSet());
}
}
//------------------------------------------------------------------------------------------//
// Data saving handlers
public function addpage() {
$className = $_REQUEST['PageType'] ? $_REQUEST['PageType'] : "Page";
$parent = $_REQUEST['ParentID'] ? $_REQUEST['ParentID'] : 0;
$suffix = $_REQUEST['Suffix'] ? "-" . $_REQUEST['Suffix'] : null;
if(is_numeric($parent)) $parentObj = DataObject::get_by_id("SiteTree", $parent);
if(!$parentObj || !$parentObj->ID) $parent = 0;
$p = $this->getNewItem("new-$className-$parent".$suffix );
// $p->write();
$p->CheckedPublicationDifferences = $p->AddedToStage = true;
return $this->returnItemToUser($p);
}
public function getNewItem($id, $setID = true) {
list($dummy, $className, $parentID, $suffix) = explode('-',$id);
$newItem = new $className();
if( !$suffix ) {
$sessionTag = "NewItems." . $parentID . "." . $className;
if(Session::get($sessionTag)) {
$suffix = '-' . Session::get($sessionTag);
Session::set($sessionTag, Session::get($sessionTag) + 1);
}
else
Session::set($sessionTag, 1);
$id = $id . $suffix;
}
$newItem->Title = "New $className";
$newItem->URLSegment = "new-" . strtolower($className);
$newItem->ClassName = $className;
$newItem->ParentID = $parentID;
if($newItem->fieldExists('Sort')) {
$newItem->Sort = DB::query("SELECT MAX(Sort) FROM SiteTree WHERE ParentID = '" . Convert::raw2sql($parentID) . "'")->value() + 1;
}
if( Member::currentUser() )
$newItem->OwnerID = Member::currentUser()->ID;
if($setID) $newItem->ID = $id;
return $newItem;
}
public function Link($action = null) {
return "admin/$action";
}
public function publish($urlParams, $form) {
$id = $_REQUEST['ID'];
$record = DataObject::get_by_id("SiteTree", $id);
$form->saveInto($record);
$this->performPublish($record);
return $this->tellBrowserAboutPublicationChange($record, "Published '$record->Title' successfully");
}
public function deletefromlive($urlParams, $form) {
$id = $_REQUEST['ID'];
Versioned::reading_stage('Live');
$record = DataObject::get_by_id("SiteTree", $id);
// before deleting the records, get the descendants of this tree
if($record) {
$descendantIDs = $record->getDescendantIDList('SiteTree');
// then delete them from the live site too
foreach( $descendantIDs as $descID )
if( $descendant = DataObject::get_by_id('SiteTree', $descID) ) {
$descendant->delete();
$descendantsRemoved++;
}
// delete the record
$record->delete();
}
Versioned::reading_stage('Stage');
if($descendantsRemoved) {
$descRemoved = " and $descendantsRemoved descendants";
}
$title = Convert::raw2js($record->Title);
FormResponse::add($this->deleteTreeNodeJS($record));
FormResponse::status_message("Deleted '$title'$descRemoved from live site", 'good');
return FormResponse::respond();
}
/**
* Actually perform the publication step
*/
public function performPublish($record) {
$record->AssignedToID = 0;
$record->RequestedByID = 0;
$record->Status = "Published";
//$record->PublishedByID = Member::currentUser()->ID;
$record->write();
$record->publish("Stage", "Live");
// Fix the sort order for this page's siblings
DB::query("UPDATE SiteTree_Live
INNER JOIN SiteTree ON SiteTree_Live.ID = SiteTree.ID
SET SiteTree_Live.Sort = SiteTree.Sort
WHERE SiteTree_Live.ParentID = " . sprintf('%d', $record->ParentID));
}
public function revert($urlParams, $form) {
$id = $_REQUEST['ID'];
Versioned::reading_stage('Live');
$obj = DataObject::get_by_id("SiteTree", $id);
Versioned::reading_stage('Stage');
$obj->publish("Live", "Stage");
$title = Convert::raw2js($obj->Title);
FormResponse::get_page($id);
FormResponse::add("$('sitetree').setNodeTitle($id, '$title');");
FormResponse::status_message("Restored '$title' successfully",'good');
return FormResponse::respond();
}
public function delete($urlParams, $form) {
$id = $_REQUEST['ID'];
$record = DataObject::get_one("SiteTree", "SiteTree.ID = $id");
$record->delete();
Director::redirectBack();
}
//------------------------------------------------------------------------------------------//
// Workflow handlers
/**
* Send this page on to another user for review
*/
function submit() {
$page = DataObject::get_by_id("SiteTree", $_REQUEST['ID']);
$recipient = DataObject::get_by_id("Member", $_REQUEST['RecipientID']);
if(!$recipient) user_error("CMSMain::submit() Can't find recipient #$_REQUEST[RecipientID]", E_USER_ERROR);
$comment = new WorkflowPageComment();
$comment->Comment = $_REQUEST['Message'];
$comment->PageID = $page->ID;
$comment->AuthorID = Member::currentUserID();
$comment->Recipient = $recipient;
$comment->Action = $_REQUEST['Status'];
$comment->write();
$emailData = $page->customise(array(
"Message" => $_REQUEST['Message'],
"Recipient" => $recipient,
"Sender" => Member::currentUser(),
"ApproveLink" => "admin/approve/$page->ID",
"EditLink" => "admin/show/$page->ID",
"StageLink" => "$page->URLSegment/?stage=Stage",
));
$email = new Page_WorkflowSubmitEmail();
$email->populateTemplate($emailData);
$email->send();
$page->AssignedToID = $recipient->ID;
$page->RequestedByID = Member::currentUserID();
$page->Status = $_REQUEST['Status'];
$page->writeWithoutVersion();
FormResponse::status_message("Sent to $recipient->FirstName $recipient->Surname for approval.","good");
return FormResponse::respond();
}
function getpagemembers() {
$relationName = $_REQUEST['SecurityLevel'];
$pageID = $this->urlParams['ID'];
$page = DataObject::get_by_id('SiteTree',$pageID);
if($page) {
foreach($page->$relationName() as $editorGroup) $groupIDs[] = $editorGroup->ID;
if($groupIDs) {
$groupList = implode(", ", $groupIDs);
$members = DataObject::get("Member","","",
"INNER JOIN `Group_Members` ON `Group_Members`.MemberID = `Member`.ID AND `Group_Members`.GroupID IN ($groupList)");
}
if($members) {
if( $page->RequestedByID )
$members->shift( $page->RequestedBy() );
foreach($members as $editor) {
$options .= "<option value=\"$editor->ID\">$editor->FirstName $editor->Surname ($editor->Email)</option>";
}
} else {
$options = "<option>(no-one available)</option>";
}
return <<<HTML
<label class="left">Send to</label>
<select name="RecipientID">$options</select>
HTML;
} else {
user_error("CMSMain::getpagemembers() Cannot find page #$pageID", E_USER_ERROR);
}
}
function getMembersByGroup() {
$group = DataObject::get_by_id("Group", $this->urlParams['ID']);
if($group){
$memberList = new MemberList('Users', $group);
$memberList->setController($this);
return $memberList->renderWith('MemberList');
}else{
return user_error("CMSMain::getpagemembers() Cannot find Group #$group->ID", E_USER_ERROR);
}
}
function addmember() {
SecurityAdmin::addmember($this->stat('subitem_class'));
}
function tasklist() {
$tasks = DataObject::get("Page", "AssignedToID = " . Member::currentUserID(), "Created DESC");
if($tasks) {
$data = new ArrayData(array(
"Tasks" => $tasks,
"Message" => "You have work to do on these <b>" . $tasks->Count() . "</b> pages.",
));
} else {
$data = new ArrayData(array(
"Message" => "You have nothing assigned to you.",
));
}
return $data->renderWith("TaskList");
}
function waitingon() {
$tasks = DataObject::get("Page", "RequestedByID = " . Member::currentUserID(), "Created DESC");
if($tasks) {
$data = new ArrayData(array(
"Tasks" => $tasks,
"Message" => "You are waiting on other people to work on these <b>" . $tasks->Count() . "</b> pages.",
));
} else {
$data = new ArrayData(array(
"Message" => "You aren't waiting on anybody.",
));
}
return $data->renderWith("WaitingOn");
}
function comments() {
if($this->urlParams['ID']) {
$comments = DataObject::get("WorkflowPageComment", "PageID = " . $this->urlParams['ID'], "Created DESC");
$data = new ArrayData(array(
"Comments" => $comments,
));
return $data->renderWith("CommentList");
}
}
/**
* Return a dropdown for selecting reports
*/
function ReportSelector() {
$reports = ClassInfo::subclassesFor("SideReport");
$options[""] = "(Choose a report)";
foreach($reports as $report) {
if($report != 'SideReport') $options[$report] = singleton($report)->title();
}
return new DropdownField("ReportSelector","Report",$options);
}
/**
* Get the content for a side report
*/
function sidereport() {
$reportClass = $this->urlParams['ID'];
$report = new $reportClass();
return $report->getHTML();
}
/**
* Get the versions of the current page
*/
function versions() {
$pageID = $this->urlParams['ID'];
$page = $this->getRecord($pageID);
if($page) {
$versions = $page->allVersions($_REQUEST['unpublished'] ? "" : "`SiteTree_versions`.WasPublished = 1");
return array(
'Versions' => $versions,
);
} else {
return "Can't find page #$pageID";
}
}
/**
* Roll a page back to a previous version
*/
function rollback() {
if($_REQUEST['Version']) {
$record = $this->performRollback($_REQUEST['ID'], $_REQUEST['Version']);
echo "Rolled back to version #$_REQUEST[Version]. New version number is #$record->Version";
} else {
$record = $this->performRollback($_REQUEST['ID'], "Live");
echo "Rolled back to published version. New version number is #$record->Version";
}
}
function unpublish() {
$SQL_id = Convert::raw2sql($_REQUEST['ID']);
$page = DataObject::get_by_id("SiteTree", $SQL_id);
$page->deleteFromStage('Live');
$page->flushCache();
$page = DataObject::get_by_id("SiteTree", $SQL_id);
$page->Status = "Unpublished";
$page->write();
return $this->tellBrowserAboutPublicationChange($page, "Removed '$page->Title' from the published site");
}
/**
* Return a few pieces of information about a change to a page
* - Send the new status message
* - Update the action buttons
* - Update the treenote
* - Send a status message
*/
function tellBrowserAboutPublicationChange($page, $statusMessage) {
$JS_title = Convert::raw2js($page->TreeTitle());
$JS_stageURL = Convert::raw2js(DB::query("SELECT URLSegment FROM SiteTree WHERE ID = $page->ID")->value());
$JS_liveURL = Convert::raw2js(DB::query("SELECT URLSegment FROM SiteTree_Live WHERE ID = $page->ID")->value());
FormResponse::add($this->getActionUpdateJS($page));
FormResponse::update_status($page->Status);
FormResponse::add("\$('sitetree').setNodeTitle($page->ID, '$JS_title')");
FormResponse::status_message($statusMessage, 'good');
FormResponse::add("$('Form_EditForm').elements.StageURLSegment.value = '$JS_stageURL'");
FormResponse::add("$('Form_EditForm').elements.LiveURLSegment.value = '$JS_liveURL'");
FormResponse::add("$('Form_EditForm').notify('PagePublished', $('Form_EditForm').elements.ID.value);");
return FormResponse::respond();
}
function performRollback($id, $version) {
$record = DataObject::get_by_id($this->stat('tree_class'), $id);
$record->publish($version, "Stage", true);
$record->AssignedToID = 0;
$record->RequestedByID = 0;
$record->Status = "Saved (update)";
$record->writeWithoutVersion();
return $record;
}
function getversion() {
$id = $this->urlParams['ID'];
$version = $this->urlParams['OtherID'];
$record = Versioned::get_version("SiteTree", $id, $version);
if($record) {
$fields = $record->getCMSFields($this);
$fields->removeByName("Status");
$fields->push(new HiddenField("ID"));
$fields->push(new HiddenField("Version"));
$fields->insertBefore(new HeaderField("You are viewing version #$version, created " . $record->obj('LastEdited')->Ago()), "Root");
$actions = new FieldSet(
new FormAction("email", "Email"),
new FormAction("print", "Print"),
new FormAction("rollback", "Roll back to this version")
);
// encode the message to appear in the body of the email
$archiveURL = Director::absoluteBaseURL() . $record->URLSegment . '?archiveDate=' . $record->obj('LastEdited')->URLDate();
$archiveEmailMessage = urlencode( $this->customise( array( 'ArchiveDate' => $record->obj('LastEdited'), 'ArchiveURL' => $archiveURL ) )->renderWith( 'ViewArchivedEmail' ) );
$archiveEmailMessage = preg_replace( '/\+/', '%20', $archiveEmailMessage );
$fields->push( new HiddenField( 'ArchiveEmailMessage', '', $archiveEmailMessage ) );
$fields->push( new HiddenField( 'ArchiveEmailSubject', '', preg_replace( '/\+/', '%20', urlencode( 'Archived version of ' . $record->Title ) ) ) );
$fields->push( new HiddenField( 'ArchiveURL', '', $archiveURL ) );
$form = new Form($this, "EditForm", $fields, $actions);
$form->loadDataFrom($record);
$form->loadDataFrom(array(
"ID" => $id,
"Version" => $version,
));
$form->makeReadonly();
$templateData = $this->customise(array(
"EditForm" => $form
));
SSViewer::setOption('rewriteHashlinks', false);
$result = $templateData->renderWith($this->class . '_right');
$parts = split('</?form[^>]*>', $result);
return $parts[sizeof($parts)-2];
}
}
function compareversions() {
$id = $this->urlParams['ID'];
$version1 = $_REQUEST['From'];
$version2 = $_REQUEST['To'];
if( $version1 > $version2 ) {
$toVersion = $version1;
$fromVersion = $version2;
} else {
$toVersion = $version2;
$fromVersion = $version1;
}
$page = DataObject::get_by_id("SiteTree", $id);
$record = $page->compareVersions($fromVersion, $toVersion);
if($record) {
$fields = $record->getCMSFields($this);
$fields->push(new HiddenField("ID"));
$fields->push(new HiddenField("Version"));
$fields->insertBefore(new HeaderField("You are comparing versions #$fromVersion and #$toVersion"), "Root");
$actions = new FieldSet();
$form = new Form($this, "EditForm", $fields, $actions);
$form->loadDataFrom($record);
$form->loadDataFrom(array(
"ID" => $id,
"Version" => $version,
));
$form->makeReadonly();
foreach($form->Fields()->dataFields() as $field) {
$field->dontEscape = true;
}
return $this->sendFormToBrowser(array(
"EditForm" => $form
));
}
}
function sendFormToBrowser($templateData) {
if(Director::is_ajax()) {
SSViewer::setOption('rewriteHashlinks', false);
$result = $this->customise($templateData)->renderWith($this->class . '_right');
$parts = split('</?form[^>]*>', $result);
return $parts[sizeof($parts)-2];
} else {
return array(
"Right" => $this->customise($templateData)->renderWith($this->class . '_right'),
);
}
}
function dialog() {
Requirements::clear();
$buttons = new DataObjectSet;
if($_REQUEST['Buttons']) foreach($_REQUEST['Buttons'] as $button) {
list($name, $title) = explode(',',$button,2);
$buttons->push(new ArrayData(array(
"Name" => $name,
"Title" => $title,
)));
}
return array(
"Message" => htmlentities($_REQUEST['Message']),
"Buttons" => $buttons,
"Modal" => $_REQUEST['Modal'] ? true : false,
);
}
function savedialog() {
Requirements::clear();
Requirements::css('cms/css/dialog.css');
Requirements::javascript('jsparty/prototype.js');
Requirements::javascript('jsparty/behaviour.js');
Requirements::javascript('jsparty/prototype_improvements.js');
Requirements::javascript('cms/javascript/dialog.js');
$message = "You have unsaved changes. Would you like to save them?";
$buttons = "<button name=\"save\">Save changes</button><button name=\"discard\">Discard changes</button><button name=\"cancel\">Stay on this page</button>";
return $this->customise( array(
'Message' => $message,
'Buttons' => $buttons,
'DialogType' => 'alert'
))->renderWith( 'Dialog' );
}
function canceldraftchangesdialog() {
Requirements::clear();
Requirements::css('cms/css/dialog.css');
Requirements::javascript('jsparty/prototype.js');
Requirements::javascript('jsparty/behaviour.js');
Requirements::javascript('jsparty/prototype_improvements.js');
Requirements::javascript('cms/javascript/dialog.js');
$message = "Do you really want to copy the published content to the stage site?";
$buttons = "<button name=\"OK\">OK</button><button name=\"Cancel\">Cancel</button>";
return $this->customise( array(
'Message' => $message,
'Buttons' => $buttons,
'DialogType' => 'alert'
))->renderWith('Dialog');
}
/**
* Delete a number of items.
* This code supports notification
*/
public function deleteitems() {
// This method can't be called without ajax.
if(!Director::is_ajax()) {
Director::redirectBack();
return;
}
$ids = split(' *, *', $_REQUEST['csvIDs']);
$notifications = array();
$idList = array();
// make sure all the ids are numeric.
// Add all the children to the list of IDs if they are missing
foreach($ids as $id) {
$brokenPageList = '';
if(is_numeric($id)) {
$record = DataObject::get_by_id($this->stat('tree_class'), $id);
// if(!$record) Debug::message( "Can't find record #$id" );
if($record) {
// add all the children for this record if they are not already in the list
// this check is a little slower but will prevent circular dependencies
// (should they exist, which they probably shouldn't) from causing
// the function to not terminate
$children = $record->AllChildren();
if( $children )
foreach( $children as $child )
if( array_search( $child->ID, $ids ) !== FALSE )
$ids[] = $child->ID;
if($record->hasMethod('BackLinkTracking')) {
$brokenPages = $record->BackLinkTracking();
foreach($brokenPages as $brokenPage) {
$brokenPageList .= "<li style=\"font-size: 65%\">" . $brokenPage->Breadcrumbs(3, true) . "</li>";
$brokenPage->HasBrokenLink = true;
$notifications[$brokenPage->OwnerID][] = $brokenPage;
$brokenPage->writeWithoutVersion();
}
}
$record->delete();
$record->destroy();
// DataObject::delete_by_id($this->stat('tree_class'), $id);
$record->CheckedPublicationDifferences = $record->DeletedFromStage = true;
// check to see if the record exists on the live site, if it doesn't remove the tree node
// $_REQUEST['showqueries'] = 1 ;
$liveRecord = Versioned::get_one_by_stage( $this->stat('tree_class'), 'Live', "`{$this->stat('tree_class')}`.`ID`={$id}");
if($liveRecord) {
$title = Convert::raw2js($record->TreeTitle());
FormResponse::add("$('sitetree').setNodeTitle($record->OldID, '$title');");
FormResponse::add("$('Form_EditForm').reloadIfSetTo($record->OldID);");
} else {
FormResponse::add("var node = $('sitetree').getTreeNodeByIdx('$id');");
FormResponse::add("if(node.parentTreeNode) node.parentTreeNode.removeTreeNode(node);");
FormResponse::add("$('Form_EditForm').reloadIfSetTo($record->OldID);");
}
}
}
}
if($notifications) foreach($notifications as $memberID => $pages) {
if(class_exists('Page_BrokenLinkEmail')) {
$email = new Page_BrokenLinkEmail();
$email->populateTemplate(new ArrayData(array(
"Recipient" => DataObject::get_by_id("Member", $memberID),
"BrokenPages" => new DataObjectSet($pages),
)));
$email->debug();
$email->send();
}
}
$s = sizeof($ids) > 1 ? "s" : "";
$message = sizeof($ids) . " page$s deleted.";
if($brokenPageList != '') {
$message .= " The following pages now have broken links:<ul>" . addslashes($brokenPageList) . "</ul>Their owners have been emailed and they will fix up those pages.";
}
FormResponse::status_message($message);
return FormResponse::respond();
}
function buildbrokenlinks() {
if($this->urlParams['ID']) {
$newPageSet[] = DataObject::get_by_id("Page", $this->urlParams['ID']);
} else {
$pages = DataObject::get("Page");
foreach($pages as $page) $newPageSet[] = $page;
$pages = null;
}
$content = new HtmlEditorField('Content');
$download = new HtmlEditorField('Download');
foreach($newPageSet as $i => $page) {
$page->HasBrokenLink = 0;
$page->HasBrokenFile = 0;
$lastUsage = (memory_get_usage() - $lastPoint);
$lastPoint = memory_get_usage();
$content->setValue($page->Content);
$content->saveInto($page);
$download->setValue($page->Download);
$download->saveInto($page);
echo "<li>$page->Title (link:$page->HasBrokenLink, file:$page->HasBrokenFile)";
$page->writeWithoutVersion();
$page->destroy();
$newPageSet[$i] = null;
}
}
function AddPageOptionsForm() {
$pageTypes = array();
foreach( $this->PageTypes() as $arrayData ) {
$pageTypes[$arrayData->getField('ClassName')] = $arrayData->getField('AddAction');
}
return new Form($this, "AddPageOptionsForm", new FieldSet(
new HiddenField("ParentID"),
new DropdownField("PageType", "", $pageTypes)
// "Page to copy" => new TreeDropdownField("DuplicateSection", "", "SiteTree"),
),
new FieldSet(
new FormAction("addpage", "Go")
));
}
/**
* Helper function to get page count
*/
function getpagecount() {
ini_set('max_execution_time',300);
$excludePages = split(" *, *", $_GET['exclude']);
$pages = DataObject::get("SiteTree", "ParentID = 0");
foreach($pages as $page) $pageArr[] = $page;
while(list($i,$page) = each($pageArr)) {
if(!in_array($page->URLSegment, $excludePages)) {
if($children = $page->AllChildren()) {
foreach($children as $child) $pageArr[] = $child;
}
if(!$_GET['onlywithcontent'] || strlen(Convert::xml2raw($page->Content)) > 100) {
echo "<li>" . $page->Breadcrumbs(null, true) . "</li>";
$count++;
} else {
echo "<li style=\"color: #777\">" . $page->Breadcrumbs(null, true) . " - no content</li>";
}
}
}
echo "<p>Total pages: $count</p>";
}
function publishall() {
ini_set("memory_limit","100M");
ini_set('max_execution_time', 300);
if(isset($_POST['confirm'])) {
$pages = DataObject::get("SiteTree");
$count = 0;
foreach($pages as $page) {
$this->performPublish($page);
$page->destroy();
unset($page);
$count++;
echo "<li>$count";
}
echo "Done: Published $count pages";
} else {
echo <<<HTML
<h1>"Publish All" functionality</h1>
<p>Pressing this button will do the equivalent of going to every page and pressing "publish". It's
intended to be used after there have been massive edits of the content, such as when the site was
first built.</p>
<form method="post" action="publishall">
<input type="submit" name="confirm" value="Please publish every page in the site, copying content stage to live" />
</form>
HTML;
}
}
function restorepage() {
if($id = $this->urlParams['ID']) {
$restoredPage = Versioned::get_latest_version("SiteTree", $id);
$restoredPage->ID = $restoredPage->RecordID;
if(!DB::query("SELECT ID FROM SiteTree WHERE ID = $restoredPage->ID")->value()) {
DB::query("INSERT INTO SiteTree SET ID = $restoredPage->ID");
}
$restoredPage->forceChange();
$restoredPage->writeWithoutVersion();
Debug::show($restoredPage);
} else {
echo "visit restorepage/(ID)";
}
}
function duplicate() {
if(($id = $this->urlParams['ID']) && is_numeric($id)) {
$page = DataObject::get_by_id("SiteTree", $id);
$newPage = $page->duplicate();
return $this->returnItemToUser($newPage);
} else {
user_error("CMSMain::duplicate() Bad ID: '$id'", E_USER_WARNING);
}
}
// HACK HACK HACK - Dont remove without telling simon ;-)
/**
* This is only used by parents inc.
* TODO Work out a better way of handling control to the individual page objects.
*/
function sethottip($data,$form) {
$page = DataObject::get_by_id("SiteTree", $_REQUEST['ID']);
return $page->sethottip($data,$form);
}
/**
* This is only used by parents inc.
* TODO Work out a better way of handling control to the individual page objects.
*/
function notifyInvitation($data,$form) {
$page = DataObject::get_by_id("SiteTree", $_REQUEST['ID']);
return $page->notifyInvitation($data,$form);
}
function testInvitation($data,$form) {
$page = DataObject::get_by_id("SiteTree", $_REQUEST['ID']);
return $page->testInvitation($data,$form);
}
/**
* Use this as an action handler for custom CMS buttons.
*/
function callPageMethod($data, $form) {
$methodName = $form->buttonClicked()->extraData();
$record = $this->CurrentPage();
return $record->$methodName($data, $form);
}
/**
* Provide the permission codes used by LeftAndMain.
* Can't put it on LeftAndMain since that's an abstract base class.
*/
function providePermissions() {
$classes = ClassInfo::subclassesFor('LeftAndMain');
foreach($classes as $class) {
$perms["CMS_ACCESS_" . $class] = "Access to $class in CMS";
}
return $perms;
}
}
?>

795
code/Diff.php Executable file
View File

@ -0,0 +1,795 @@
<?php
// difflib.php
//
// A PHP diff engine for phpwiki.
//
// Copyright (C) 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
// You may copy this code freely under the conditions of the GPL.
//
// FIXME: possibly remove assert()'s for production version?
// PHP3 does not have assert()
define('USE_ASSERTS', function_exists('assert'));
class _DiffOp {
var $type;
var $orig;
var $final;
function reverse() {
trigger_error("pure virtual", E_USER_ERROR);
}
function norig() {
return $this->orig ? sizeof($this->orig) : 0;
}
function nfinal() {
return $this->final ? sizeof($this->final) : 0;
}
}
class _DiffOp_Copy extends _DiffOp {
var $type = 'copy';
function _DiffOp_Copy ($orig, $final = false) {
if (!is_array($final))
$final = $orig;
$this->orig = $orig;
$this->final = $final;
}
function reverse() {
return new _DiffOp_Copy($this->final, $this->orig);
}
}
class _DiffOp_Delete extends _DiffOp {
var $type = 'delete';
function _DiffOp_Delete ($lines) {
$this->orig = $lines;
$this->final = false;
}
function reverse() {
return new _DiffOp_Add($this->orig);
}
}
class _DiffOp_Add extends _DiffOp {
var $type = 'add';
function _DiffOp_Add ($lines) {
$this->final = $lines;
$this->orig = false;
}
function reverse() {
return new _DiffOp_Delete($this->final);
}
}
class _DiffOp_Change extends _DiffOp {
var $type = 'change';
function _DiffOp_Change ($orig, $final) {
$this->orig = $orig;
$this->final = $final;
}
function reverse() {
return new _DiffOp_Change($this->final, $this->orig);
}
}
/**
* Class used internally by Diff to actually compute the diffs.
*
* The algorithm used here is mostly lifted from the perl module
* Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
* http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
*
* More ideas are taken from:
* http://www.ics.uci.edu/~eppstein/161/960229.html
*
* Some ideas are (and a bit of code) are from from analyze.c, from GNU
* diffutils-2.7, which can be found at:
* ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
*
* Finally, some ideas (subdivision by NCHUNKS > 2, and some optimizations)
* are my own.
*
* @author Geoffrey T. Dairiki
* @access private
*/
class _DiffEngine
{
function diff ($from_lines, $to_lines) {
$n_from = sizeof($from_lines);
$n_to = sizeof($to_lines);
$this->xchanged = $this->ychanged = array();
$this->xv = $this->yv = array();
$this->xind = $this->yind = array();
unset($this->seq);
unset($this->in_seq);
unset($this->lcs);
// Skip leading common lines.
for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
if ($from_lines[$skip] != $to_lines[$skip])
break;
$this->xchanged[$skip] = $this->ychanged[$skip] = false;
}
// Skip trailing common lines.
$xi = $n_from; $yi = $n_to;
for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
if ($from_lines[$xi] != $to_lines[$yi])
break;
$this->xchanged[$xi] = $this->ychanged[$yi] = false;
}
// Ignore lines which do not exist in both files.
for ($xi = $skip; $xi < $n_from - $endskip; $xi++)
$xhash[$from_lines[$xi]] = 1;
for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
$line = $to_lines[$yi];
if ( ($this->ychanged[$yi] = empty($xhash[$line])) )
continue;
$yhash[$line] = 1;
$this->yv[] = $line;
$this->yind[] = $yi;
}
for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
$line = $from_lines[$xi];
if ( ($this->xchanged[$xi] = empty($yhash[$line])) )
continue;
$this->xv[] = $line;
$this->xind[] = $xi;
}
// Find the LCS.
$this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv));
// Merge edits when possible
$this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged);
$this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged);
// Compute the edit operations.
$edits = array();
$xi = $yi = 0;
while ($xi < $n_from || $yi < $n_to) {
USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
// Skip matching "snake".
$copy = array();
while ( $xi < $n_from && $yi < $n_to
&& !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
$copy[] = $from_lines[$xi++];
++$yi;
}
if ($copy)
$edits[] = new _DiffOp_Copy($copy);
// Find deletes & adds.
$delete = array();
while ($xi < $n_from && $this->xchanged[$xi])
$delete[] = $from_lines[$xi++];
$add = array();
while ($yi < $n_to && $this->ychanged[$yi])
$add[] = $to_lines[$yi++];
if ($delete && $add)
$edits[] = new _DiffOp_Change($delete, $add);
elseif ($delete)
$edits[] = new _DiffOp_Delete($delete);
elseif ($add)
$edits[] = new _DiffOp_Add($add);
}
return $edits;
}
/* Divide the Largest Common Subsequence (LCS) of the sequences
* [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally
* sized segments.
*
* Returns (LCS, PTS). LCS is the length of the LCS. PTS is an
* array of NCHUNKS+1 (X, Y) indexes giving the diving points between
* sub sequences. The first sub-sequence is contained in [X0, X1),
* [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note
* that (X0, Y0) == (XOFF, YOFF) and
* (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM).
*
* This function assumes that the first lines of the specified portions
* of the two files do not match, and likewise that the last lines do not
* match. The caller must trim matching lines from the beginning and end
* of the portions it is going to specify.
*/
function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) {
$flip = false;
if ($xlim - $xoff > $ylim - $yoff) {
// Things seems faster (I'm not sure I understand why)
// when the shortest sequence in X.
$flip = true;
list ($xoff, $xlim, $yoff, $ylim)
= array( $yoff, $ylim, $xoff, $xlim);
}
if ($flip)
for ($i = $ylim - 1; $i >= $yoff; $i--)
$ymatches[$this->xv[$i]][] = $i;
else
for ($i = $ylim - 1; $i >= $yoff; $i--)
$ymatches[$this->yv[$i]][] = $i;
$this->lcs = 0;
$this->seq[0]= $yoff - 1;
$this->in_seq = array();
$ymids[0] = array();
$numer = $xlim - $xoff + $nchunks - 1;
$x = $xoff;
for ($chunk = 0; $chunk < $nchunks; $chunk++) {
if ($chunk > 0)
for ($i = 0; $i <= $this->lcs; $i++)
$ymids[$i][$chunk-1] = $this->seq[$i];
$x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks);
for ( ; $x < $x1; $x++) {
$line = $flip ? $this->yv[$x] : $this->xv[$x];
if (empty($ymatches[$line]))
continue;
$matches = $ymatches[$line];
reset($matches);
while (list ($junk, $y) = each($matches))
if (empty($this->in_seq[$y])) {
$k = $this->_lcs_pos($y);
USE_ASSERTS && assert($k > 0);
$ymids[$k] = $ymids[$k-1];
break;
}
while (list ($junk, $y) = each($matches)) {
if ($y > $this->seq[$k-1]) {
USE_ASSERTS && assert($y < $this->seq[$k]);
// Optimization: this is a common case:
// next match is just replacing previous match.
$this->in_seq[$this->seq[$k]] = false;
$this->seq[$k] = $y;
$this->in_seq[$y] = 1;
}
else if (empty($this->in_seq[$y])) {
$k = $this->_lcs_pos($y);
USE_ASSERTS && assert($k > 0);
$ymids[$k] = $ymids[$k-1];
}
}
}
}
$seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
$ymid = $ymids[$this->lcs];
for ($n = 0; $n < $nchunks - 1; $n++) {
$x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks);
$y1 = $ymid[$n] + 1;
$seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
}
$seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
return array($this->lcs, $seps);
}
function _lcs_pos ($ypos) {
$end = $this->lcs;
if ($end == 0 || $ypos > $this->seq[$end]) {
$this->seq[++$this->lcs] = $ypos;
$this->in_seq[$ypos] = 1;
return $this->lcs;
}
$beg = 1;
while ($beg < $end) {
$mid = (int)(($beg + $end) / 2);
if ( $ypos > $this->seq[$mid] )
$beg = $mid + 1;
else
$end = $mid;
}
USE_ASSERTS && assert($ypos != $this->seq[$end]);
$this->in_seq[$this->seq[$end]] = false;
$this->seq[$end] = $ypos;
$this->in_seq[$ypos] = 1;
return $end;
}
/* Find LCS of two sequences.
*
* The results are recorded in the vectors $this->{x,y}changed[], by
* storing a 1 in the element for each line that is an insertion
* or deletion (ie. is not in the LCS).
*
* The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
*
* Note that XLIM, YLIM are exclusive bounds.
* All line numbers are origin-0 and discarded lines are not counted.
*/
function _compareseq ($xoff, $xlim, $yoff, $ylim) {
// Slide down the bottom initial diagonal.
while ($xoff < $xlim && $yoff < $ylim
&& $this->xv[$xoff] == $this->yv[$yoff]) {
++$xoff;
++$yoff;
}
// Slide up the top initial diagonal.
while ($xlim > $xoff && $ylim > $yoff
&& $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
--$xlim;
--$ylim;
}
if ($xoff == $xlim || $yoff == $ylim)
$lcs = 0;
else {
// This is ad hoc but seems to work well.
//$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5);
//$nchunks = max(2,min(8,(int)$nchunks));
$nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
list ($lcs, $seps)
= $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks);
}
if ($lcs == 0) {
// X and Y sequences have no common subsequence:
// mark all changed.
while ($yoff < $ylim)
$this->ychanged[$this->yind[$yoff++]] = 1;
while ($xoff < $xlim)
$this->xchanged[$this->xind[$xoff++]] = 1;
}
else {
// Use the partitions to split this problem into subproblems.
reset($seps);
$pt1 = $seps[0];
while ($pt2 = next($seps)) {
$this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
$pt1 = $pt2;
}
}
}
/* Adjust inserts/deletes of identical lines to join changes
* as much as possible.
*
* We do something when a run of changed lines include a
* line at one end and has an excluded, identical line at the other.
* We are free to choose which identical line is included.
* `compareseq' usually chooses the one at the beginning,
* but usually it is cleaner to consider the following identical line
* to be the "change".
*
* This is extracted verbatim from analyze.c (GNU diffutils-2.7).
*/
function _shift_boundaries ($lines, &$changed, $other_changed) {
$i = 0;
$j = 0;
USE_ASSERTS && assert('sizeof($lines) == sizeof($changed)');
$len = sizeof($lines);
$other_len = sizeof($other_changed);
while (1) {
/*
* Scan forwards to find beginning of another run of changes.
* Also keep track of the corresponding point in the other file.
*
* Throughout this code, $i and $j are adjusted together so that
* the first $i elements of $changed and the first $j elements
* of $other_changed both contain the same number of zeros
* (unchanged lines).
* Furthermore, $j is always kept so that $j == $other_len or
* $other_changed[$j] == false.
*/
while ($j < $other_len && $other_changed[$j])
$j++;
while ($i < $len && ! $changed[$i]) {
USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
$i++; $j++;
while ($j < $other_len && $other_changed[$j])
$j++;
}
if ($i == $len)
break;
$start = $i;
// Find the end of this run of changes.
while (++$i < $len && $changed[$i])
continue;
do {
/*
* Record the length of this run of changes, so that
* we can later determine whether the run has grown.
*/
$runlength = $i - $start;
/*
* Move the changed region back, so long as the
* previous unchanged line matches the last changed one.
* This merges with previous changed regions.
*/
while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
$changed[--$start] = 1;
$changed[--$i] = false;
while ($start > 0 && $changed[$start - 1])
$start--;
USE_ASSERTS && assert('$j > 0');
while ($other_changed[--$j])
continue;
USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
}
/*
* Set CORRESPONDING to the end of the changed run, at the last
* point where it corresponds to a changed run in the other file.
* CORRESPONDING == LEN means no such point has been found.
*/
$corresponding = $j < $other_len ? $i : $len;
/*
* Move the changed region forward, so long as the
* first changed line matches the following unchanged one.
* This merges with following changed regions.
* Do this second, so that if there are no merges,
* the changed region is moved forward as far as possible.
*/
while ($i < $len && $lines[$start] == $lines[$i]) {
$changed[$start++] = false;
$changed[$i++] = 1;
while ($i < $len && $changed[$i])
$i++;
USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
$j++;
if ($j < $other_len && $other_changed[$j]) {
$corresponding = $i;
while ($j < $other_len && $other_changed[$j])
$j++;
}
}
} while ($runlength != $i - $start);
/*
* If possible, move the fully-merged run of changes
* back to a corresponding run in the other file.
*/
while ($corresponding < $i) {
$changed[--$start] = 1;
$changed[--$i] = 0;
USE_ASSERTS && assert('$j > 0');
while ($other_changed[--$j])
continue;
USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
}
}
}
}
/**
* Class representing a 'diff' between two sequences of strings.
*/
class Diff
{
var $edits;
/**
* Constructor.
* Computes diff between sequences of strings.
*
* @param $from_lines array An array of strings.
* (Typically these are lines from a file.)
* @param $to_lines array An array of strings.
*/
function Diff($from_lines, $to_lines) {
$eng = new _DiffEngine;
$this->edits = $eng->diff($from_lines, $to_lines);
//$this->_check($from_lines, $to_lines);
}
/**
* Compute reversed Diff.
*
* SYNOPSIS:
*
* $diff = new Diff($lines1, $lines2);
* $rev = $diff->reverse();
* @return object A Diff object representing the inverse of the
* original diff.
*/
function reverse () {
$rev = $this;
$rev->edits = array();
foreach ($this->edits as $edit) {
$rev->edits[] = $edit->reverse();
}
return $rev;
}
/**
* Check for empty diff.
*
* @return bool True iff two sequences were identical.
*/
function isEmpty () {
foreach ($this->edits as $edit) {
if ($edit->type != 'copy')
return false;
}
return true;
}
/**
* Compute the length of the Longest Common Subsequence (LCS).
*
* This is mostly for diagnostic purposed.
*
* @return int The length of the LCS.
*/
function lcs () {
$lcs = 0;
foreach ($this->edits as $edit) {
if ($edit->type == 'copy')
$lcs += sizeof($edit->orig);
}
return $lcs;
}
/**
* Get the original set of lines.
*
* This reconstructs the $from_lines parameter passed to the
* constructor.
*
* @return array The original sequence of strings.
*/
function orig() {
$lines = array();
foreach ($this->edits as $edit) {
if ($edit->orig)
array_splice($lines, sizeof($lines), 0, $edit->orig);
}
return $lines;
}
/**
* Get the final set of lines.
*
* This reconstructs the $to_lines parameter passed to the
* constructor.
*
* @return array The sequence of strings.
*/
function finaltext() {
$lines = array();
foreach ($this->edits as $edit) {
if ($edit->final)
array_splice($lines, sizeof($lines), 0, $edit->final);
}
return $lines;
}
/**
* Check a Diff for validity.
*
* This is here only for debugging purposes.
*/
function _check ($from_lines, $to_lines) {
if (serialize($from_lines) != serialize($this->orig()))
trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
if (serialize($to_lines) != serialize($this->finaltext()))
trigger_error("Reconstructed final doesn't match", E_USER_ERROR);
$rev = $this->reverse();
if (serialize($to_lines) != serialize($rev->orig()))
trigger_error("Reversed original doesn't match", E_USER_ERROR);
if (serialize($from_lines) != serialize($rev->finaltext()))
trigger_error("Reversed final doesn't match", E_USER_ERROR);
$prevtype = 'none';
foreach ($this->edits as $edit) {
if ( $prevtype == $edit->type )
trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
$prevtype = $edit->type;
}
$lcs = $this->lcs();
trigger_error("Diff okay: LCS = $lcs", E_USER_NOTICE);
}
static function compareHTML($from, $to) {
// First split up the content into words and tags
$set1 = self::getHTMLChunks($from);
$set2 = self::getHTMLChunks($to);
// Diff that
$diff = new Diff($set1, $set2);
// Go through everything, converting edited tags (and their content) into single chunks. Otherwise
// the generated HTML gets crusty
foreach($diff->edits as $edit) {
//echo "<li>$edit->type: " . htmlentities(implode(" " ,$edit->orig));
//if($edit->final) echo ' / ' . htmlentities(implode(" " ,$edit->final));
switch($edit->type) {
case 'copy':
$lookForTag = false;
$stuffFor[1] = $edit->orig;
$stuffFor[2] = $edit->orig;
break;
case 'change':
$lookForTag = true;
$stuffFor[1] = $edit->orig;
$stuffFor[2] = $edit->final;
break;
case 'add':
$lookForTag = true;
$stuffFor[1] = null;
$stuffFor[2] = $edit->final;
break;
case 'delete':
$lookForTag = true;
$stuffFor[1] = $edit->orig;
$stuffFor[2] = null;
break;
}
foreach($stuffFor as $listName => $chunks) {
if($chunks) {
foreach($chunks as $item) {
// $tagStack > 0 indicates that we should be tag-building
if($tagStack[$listName]) $rechunked[$listName][sizeof($rechunked[$listName])-1] .= ' ' . $item;
else $rechunked[$listName][] = $item;
if($lookForTag && $item[0] == "<" && substr($item,0,2) != "</") {
$tagStack[$listName] = 1;
} else if($tagStack[$listName]) {
if(substr($item,0,2) == "</") $tagStack[$listName]--;
else if($item[0] == "<") $tagStack[$listName]++;
}
// echo "<li>" . htmlentities($item) . " -> " .$tagStack[$listName];
}
}
}
}
// Diff the re-chunked data, turning it into maked up HTML
$diff = new Diff($rechunked[1], $rechunked[2]);
foreach($diff->edits as $edit) {
// echo "<li>$edit->type: " . htmlentities(implode(" " ,$edit->orig));
// if($edit->final) echo ' / ' . htmlentities(implode(" " ,$edit->final));
switch($edit->type) {
case 'copy':
$content .= " " . implode(" ", $edit->orig) . " ";
break;
case 'change':
$content .= " <ins>" . implode(" ", $edit->final) . "</ins> ";
$content .= " <del>" . implode(" ", $edit->orig) . "</del> ";
break;
case 'add':
$content .= " <ins>" . implode(" ", $edit->final) . "</ins> ";
break;
case 'delete':
$content .= " <del>" . implode(" ", $edit->orig) . "</del> ";
break;
}
}
// echo "<p>" . htmlentities($content) . "</p>";
return $content;
}
static function getHTMLChunks($content) {
$content = str_replace(array("&nbsp;","<", ">"),array(" "," <", "> "),$content);
$candidateChunks = split("[\t\r\n ]+", $content);
while(list($i,$item) = each($candidateChunks)) {
if($item[0] == "<") {
$newChunk = $item;
while($item[strlen($item)-1] != ">") {
list($i,$item) = each($candidateChunks);
$newChunk .= ' ' . $item;
}
$chunks[] = $newChunk;
} else {
$chunks[] = $item;
}
}
return $chunks;
}
}
/**
* FIXME: bad name.
*/
class MappedDiff
extends Diff
{
/**
* Constructor.
*
* Computes diff between sequences of strings.
*
* This can be used to compute things like
* case-insensitve diffs, or diffs which ignore
* changes in white-space.
*
* @param $from_lines array An array of strings.
* (Typically these are lines from a file.)
*
* @param $to_lines array An array of strings.
*
* @param $mapped_from_lines array This array should
* have the same size number of elements as $from_lines.
* The elements in $mapped_from_lines and
* $mapped_to_lines are what is actually compared
* when computing the diff.
*
* @param $mapped_to_lines array This array should
* have the same number of elements as $to_lines.
*/
function MappedDiff($from_lines, $to_lines,
$mapped_from_lines, $mapped_to_lines) {
assert(sizeof($from_lines) == sizeof($mapped_from_lines));
assert(sizeof($to_lines) == sizeof($mapped_to_lines));
$this->Diff($mapped_from_lines, $mapped_to_lines);
$xi = $yi = 0;
// Optimizing loop invariants:
// http://phplens.com/lens/php-book/optimizing-debugging-php.php
for ($i = 0, $max = sizeof($this->edits); $i < $max; $i++) {
$orig = &$this->edits[$i]->orig;
if (is_array($orig)) {
$orig = array_slice($from_lines, $xi, sizeof($orig));
$xi += sizeof($orig);
}
$final = &$this->edits[$i]->final;
if (is_array($final)) {
$final = array_slice($to_lines, $yi, sizeof($final));
$yi += sizeof($final);
}
}
}
}
?>

17
code/FileList.php Executable file
View File

@ -0,0 +1,17 @@
<?php
class FileList extends TableListField {
// bdc: added sort by Title as default behaviour
protected $folder;
function __construct($name, $folder) {
$this->folder = $folder;
parent::__construct($name, "File", array("Title" => "Title", "LinkedURL" => "URL"), "", "Title");
$this->Markable = true;
}
function sourceItems() {
return DataObject::get("File", "ParentID = '" . $this->folder->ID . "' AND ClassName <> 'Folder'", "Title");
}
}
?>

650
code/GenericDataAdmin.php Executable file
View File

@ -0,0 +1,650 @@
<?php
/**
* Provides a common interface for searching, viewing and editing DataObjects.
* Extend the class to adjust functionality to your specific DataObjects.
*
* @var $data_type DataObject The base class
* @var $data_type_extra Array Additional DataObjects which are included in the search.
* @var $resultColumnts Array Columnnames shown in the result-table.
*/
abstract class GenericDataAdmin extends LeftAndMain {
public $filter;
/**
* @var FieldSet Specifies which {Actions} can be performed on a resultset,
* e.g. "Export" or "Send". The form contains the resultset as CSV for further processing.
* These actions can be extended in a subclassed constructor.
*/
protected $result_actions;
/**
* @var string
*/
static $data_type;
static $data_type_extra;
/**
* Specifies which information should be listed in the results-box,
* either in "table"- or "list"-format (see {$result_format}).
*
* Format "table":
* array(
* 'AccountName' => 'AccountName'
* )
*
* Format "list":
* see {@DataObject->buildNestedUL}
*/
static $result_columns;
/**
* @var string
* Either "table" or "list". List-format also supports second level results.
*/
static $result_format = "table";
static $csv_columns;
private $results;
function __construct() {
$this->result_actions = new FieldSet(
new FormAction("export","Export as CSV")
);
parent::__construct();
}
/**
* Sets Requirements and checks for Permissions.
* Subclass this function to add custom Requirements.
*/
function init() {
parent::init();
Requirements::javascript(MCE_ROOT . "tiny_mce_src.js");
Requirements::javascript("jsparty/tiny_mce_improvements.js");
Requirements::javascript("jsparty/hover.js");
Requirements::javascript("jsparty/scriptaculous/controls.js");
Requirements::javascript("cms/javascript/SecurityAdmin.js");
Requirements::javascript("cms/javascript/CMSMain_left.js");
Requirements::javascript("cms/javascript/GenericDataAdmin_left.js");
Requirements::javascript("cms/javascript/GenericDataAdmin_right.js");
Requirements::javascript("cms/javascript/SideTabs.js");
// We don't want this showing up in every ajax-response, it should always be present in a CMS-environment
if(!Director::is_ajax()) {
Requirements::javascriptTemplate("cms/javascript/tinymce.template.js", array(
"ContentCSS" => project() . "/css/editor.css",
"BaseURL" => Director::absoluteBaseURL(),
));
}
Requirements::css("cms/css/GenericDataAdmin.css");
//For wrightgroup workshop
Requirements::css("writework/css/WorkshopCMSLayout.css");
}
function Link() {
$args = func_get_args();
return call_user_func_array( array( &$this, 'getLink' ), $args );
}
/**
* @return String
*/
function DataTypeSingular() {
return singleton($this->stat('data_type'))->singular_name();
}
/**
* @return String
*/
function DataTypePlural() {
return singleton($this->stat('data_type'))->plural_name();
}
/**
* @return Form
*/
function CreationForm() {
$plural_name = singleton($this->stat('data_type'))->plural_name();
$singular_name = singleton($this->stat('data_type'))->singular_name();
return new Form($this, 'CreationForm', new FieldSet(), new FieldSet(new FormAction("createRecord", "Create {$singular_name}")));
}
/**
* @return Form
*/
function EditForm() {
$id = isset($_REQUEST['ID']) ? $_REQUEST['ID'] : Session::get('currentPage');
if($id && DataObject::get_by_id($this->stat('data_type'), $id)) {
return $this->getEditForm($id);
}
}
// legacy
function ExportForm() {
return $this->EditForm();
}
/**
* @return Form
*/
function SearchForm() {
$fields = $this->getSearchFields();
$actions = new FieldSet($action = new FormAction("getResults", "Go"));
$searchForm = new Form($this, "SearchForm", $fields, $actions);
$searchForm->loadDataFrom($_REQUEST);
return $searchForm;
}
/**
* Determines fields and actions for the given {$data_type}, and populates
* these fields with values from {$data_type} and any connected {$data_type_extra}.
* Adds default actions ("save" and "delete") if no custom actions are found.
* Returns an empty form if no fields or actions are found (on first load).
*
* @param $id Number
* @return Form
*/
function getEditForm($id) {
if($_GET['debug_profile']) Profiler::mark('getEditForm');
$genericData = DataObject::get_by_id($this->stat('data_type'), $id);
$fields = (method_exists($genericData, getCMSFields)) ? $genericData->getCMSFields() : new FieldSet();
if(!$fields->dataFieldByName('ID')) {
$fields->push($idField = new HiddenField("ID","ID",$id));
$idField->setValue($id);
}
if(method_exists($genericData, getGenericStatus)){
$genericDataStatus = $genericData->getGenericStatus();
if($genericDataStatus){
$fields->push($dataStatusField = new ReadonlyField("GenericDataStatus", "", $genericDataStatus));
$dataStatusField -> dontEscape = true;
}
}
$actions = (method_exists($genericData, getCMSActions)) ? $genericData->getCMSActions() : new FieldSet();
if(!$actions->fieldByName('action_save')) {
$actions->push(new FormAction('save', 'Save','ajaxAction-save'));
}
if(!$actions->fieldByName('action_delete')) {
$actions->push(new FormAction('delete', 'Delete','ajaxAction-delete'));
}
$form = new Form($this, "EditForm", $fields, $actions);
if($this->stat('data_type_extra')) {
foreach ($this->stat('data_type_extra') as $oneRelated) {
$oneExtra = $genericData-> $oneRelated();
if($oneExtra) {
$allFields = $oneExtra->getAllFields();
foreach ($allFields as $k => $v) {
$fieldname = $oneRelated . "[" . $k . "]";
$allFields[$fieldname] = $v;
unset ($allFields[$k]);
}
$form->loadDataFrom($allFields);
}
}
}
$form->loadDataFrom($genericData);
$form->disableDefaultAction();
if($_GET['debug_profile']) Profiler::unmark('getEditForm');
return $form;
}
/**
* Display the results of the search.
* @return String
*/
function Results() {
$ret = "";
$singular_name = singleton($this->stat('data_type'))->singular_name();
$plural_name = singleton($this->stat('data_type'))->plural_name();
$this->filter = array(
"ClassName" => $this->stat('data_type')
);
$results = $this->performSearch();
if($results) {
$name = ($results->Count() > 1) ? $plural_name : $singular_name;
$ret .= "<H2>{$results->Count()} {$name} found:</H2>";
switch($this->stat('result_format')) {
case 'table':
$ret .= $this->getResultTable($results);
break;
case 'list':
$ret .= $this->getResultList($results);
break;
}
$ret .= $this->getResultActionsForm($results);
} else {
if($this->hasMethod('isEmptySearch') && $this->isEmptySearch()) {
$ret .="<h3>Please choose some search criteria and press 'Go'.</h3>";
} else {
$ret .="<h3>Sorry, no {$plural_name} found by this search.</h3>";
}
}
return $ret;
}
function getResults() {
if(Director::is_ajax()) {
echo $this->Results();
} else {
return $this->Results();
}
}
function getResultList($results, $link = true) {
$listBody = $results->buildNestedUL($this->stat('result_columns'));
return <<<HTML
<div class="ResultList">
$listBody
</div>
HTML;
}
/**
* @param $results
* @param $link Link the rows to their according result (evaluated by javascript)
* @return String Result-Table as HTML
*/
function getResultTable($results, $link = true) {
$tableHeader = $this->columnheader();
$tableBody = $this->columnbody($results);
return <<<HTML
<table class="ResultTable">
<thead>
$tableHeader
</thead>
<tbody>
$tableBody
</tbody>
</table>
HTML;
}
protected function columnheader(){
$content = "";
foreach( array_keys($this->stat('result_columns')) as $field ) {
$content .= $this->htmlTableCell($field);
}
return $this->htmlTableRow($content);
}
protected function columnbody($results=null) {
// shouldn't be called here, but left in for legacy
if(!$results) {
$results = $this->performSearch();
}
$body = "";
if($results){
$i=0;
foreach($results as $result){
$i++;
$html = "";
foreach($this->stat('result_columns') as $field) {
$value = $this->buildResultFieldValue($result, $field);
$html .= $this->htmlTableCell($value, $this->Link("show", $result->ID), "show", true);
}
$evenOrOdd = ($i%2)?"even":"odd";
$row = $this->htmlTableRow($html, null, $evenOrOdd);
$body .= $row;
}
}
return $body;
}
protected function listbody($results=null) {
}
/**
* @param $results Array
* @return String Form-Content
*/
function getResultActionsForm($results) {
$ret = "";
$csvValues = array();
foreach($results as $record) {
$csvValues[] = $record->ID;
}
$form = new Form(
$this,
"ExportForm",
new FieldSet(
new HiddenField("csvIDs","csvIDs",implode(",",$csvValues))
),
$this->result_actions
);
$ret = <<<HTML
<div id="Form_ResultForm">
{$form->renderWith("Form")}
</div>
HTML;
return $ret;
}
/**
* @param $result
* @param $field Mixed can be array: eg: array("FirstName", "Surname"), in which case two fields
* in database table should concatenate to one cell for report table.
* The field could be "Total->Nice" or "Order.Created->Date", intending to show its specific format.
* Caster then is "Nice" "Date" etc.
*/
protected function buildResultFieldValue($result, $field) {
if(is_array($field)) {
$i = 0;
foreach($field as $each) {
$value .= $i == 0 ? "" : "_";
$value .= $this->buildResultFieldValue($result, $each);
$i++;
}
} else {
list ($field, $caster) = explode("->", $field);
if(preg_match('/^(.+)\.(.+)$/', $field, $matches)) {
$field = $matches[2];
}
if($caster) {
// When the intending value is Created.Date, the obj need to be casted as Datetime explicitely.
if ($field == "Created" || $field == "LastEdited") {
$created = Object::create('Datetime', $result->Created, "Created");
// $created->setVal();
$value = $created->val($caster);
} else // Dealing with other field like "Total->Nice", etc.
$value = $result->obj($field)->val($caster);
} else { // Simple field, no casting
$value = $result->val($field);
}
}
return $value;
}
protected function htmlTableCell($value, $link = false, $class = "", $id = null) {
if($link) {
return "<td><a href=\"$link\" class=\"$class\" id=\"$id\">" . htmlentities($value) . "</a></td>";
} else {
return "<td>" . htmlentities($value) . "</td>";
}
}
protected function htmlTableRow($value, $link = null, $evenOrOdd = null) {
if ($link) {
return "<tr class=\"$evenOrOdd\"><a href=\"$link\">" . $value . "</a></tr>";
} else {
return "<tr class=\"$evenOrOdd\">" . $value . "</tr>";
}
}
/**
* 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}.
*/
function export() {
$now = Date("s-i-H");
$fileName = "export-$now.csv";
$csv_columns = ($this->stat('csv_columns')) ? array_values($this->stat('csv_columns')) : array_values($this->stat('result_columns'));
$fileData = "";
$fileData .= "\"" . implode("\";\"",$csv_columns) . "\"";
$fileData .= "\n";
$records = $this->performSearch();
if($records) {
foreach($records as $record) {
$columnData = array();
foreach($csv_columns as $column) {
$tmpColumnData = "\"" . str_replace("\"", "\"\"", $record->$column) . "\"";
$tmpColumnData = str_replace(array("\r", "\n"), "", $tmpColumnData);
$columnData[] = $tmpColumnData;
}
$fileData .= implode(",",$columnData);
$fileData .= "\n";
}
HTTP::sendFileToBrowser($fileData, $fileName);
} else {
user_error("No records found", E_USER_ERROR);
}
}
/**
* Save genetric data handler
*
* @return String Statusmessage
*/
function save($urlParams, $form) {
$className = $this->stat('data_type');
$id = $_REQUEST['ID'];
if(substr($id, 0, 3) != 'new') {
$generic = DataObject::get_one($className, "`$className`.ID = $id");
$generic->Status = "Saved (Update)";
} else {
$generic = new $className();
$generic->Status = "Saved (New)";
}
$form->saveInto($generic, true);
$id = $generic->write();
if($this->stat('data_type_extra')) {
foreach($this->stat('data_type_extra') as $oneRelated) {
$oneExtra = $generic->$oneRelated();
if($_REQUEST[$oneExtra->class]) {
foreach($_REQUEST[$oneExtra->class] as $field => $value) {
$oneExtra->setField($field, $value);
}
$oneExtra->write();
}
}
}
FormResponse::status_message('Saved', 'good');
FormResponse::update_status($generic->Status);
if (method_exists($this, "saveAfterCall")) {
$this->saveAfterCall($generic, $urlParams, $form);
}
return FormResponse::respond();
}
/**
* Show a single record
*
* @return Array Editing Form
*/
function show() {
Session::set('currentPage', $this->urlParams['ID']);
$editForm = $this->getEditForm($this->urlParams['ID']);
if(Director::is_ajax()) {
return $editForm->formHtmlContent();
} else {
return array (
'EditForm' => $editForm
);
}
}
/**
* Add a new DataObject
*
* @return String
*/
function createRecord() {
$baseClass = $this->stat('data_type');
$obj = new $baseClass();
$obj->write();
$editForm = $this->getEditForm($obj->ID);
return (Director::is_ajax()) ? $editForm->formHtmlContent() : array ('EditForm' => $editForm);
}
/**
* Delete a given Dataobjebt by ID
*
* @param $urlParams Array
* @param $form Form
* @return String
*/
function delete($urlParams, $form) {
$id = Convert::raw2sql($_REQUEST['ID']);
$obj = DataObject::get_by_id($this->stat('data_type'), $id);
if ($obj) {
$obj->delete();
}
// clear session data
Session::clear('currentPage');
FormResponse::status_message('Successfully deleted', 'good');
FormResponse::add("$('Form_EditForm').deleteEffect();");
return FormResponse::respond();
}
protected function getRelatedData() {
$relatedName = $_REQUEST['RelatedClass'];
$id = $_REQUEST[$relatedName]['ID'];
$baseClass = $this->stat('data_type');
$relatedClasses = singleton($baseClass)->stat('has_one');
if($id){
$relatedObject = DataObject::get_by_id($relatedClasses[$relatedName], $id);
$response .= <<<JS
$('$relatedName').unsetNewRelatedKey();
JS;
}
elseif($id !== '0'){ //in case of null;
$relatedObject = new $relatedClasses[$relatedName]();
if($parentID = $_REQUEST[$relatedName]['ParentID']){
$relatedObject->ParentID = $parentID;
}
$id = $relatedObject->write();
$response .= <<<JS
$('$relatedName').setNewRelatedKey($id);
JS;
}else{ // in case of 0
$relatedObject = new $relatedClasses[$relatedName]();
if($parentID = $_REQUEST[$relatedName]['ParentID']){
$relatedObject->ParentID = $parentID;
}
$response .= <<<JS
$('$relatedName').unsetNewRelatedKey();
JS;
}
if(Director::is_ajax()) {
$fields = $_REQUEST[$relatedName];
$response .= <<<JS
var dataArray = new Array();
JS;
foreach ($fields as $k => $v) {
$JS_newKey = Convert::raw2js($relatedName . '[' . $k . ']');
$JS_newValue = Convert::raw2js($relatedObject-> $k);
$response .=<<<JS
dataArray['$JS_newKey'] = '$JS_newValue';
JS;
}
$response .=<<<JS
$('$relatedName').updateChildren(dataArray, true);
JS;
FormResponse::add($response);
}
return FormResponse::respond();
}
protected function updateRelatedKey() {
if(Director::is_ajax()) {
$funcName = "get" . $_REQUEST['RelatedClass'] . "Dropdown";
$relatedKeyDropdown = singleton($this->stat('data_type'))->$funcName();
$relatedKeyDropdown->extraClass = "relatedDataKey";
echo $relatedKeyDropdown->FieldHolder();
} else {
Director::redirectBack();
}
}
/**
* Execute a query based on {$filter} and build a DataObjectSet
* out of the results.
*
* @return DataObjectSet
*/
abstract function performSearch();
/**
* Form fields which trigger {getResults} and {peformSearch}.
* Provide HTML in the following format to get auto-collapsing "advanced search"-fields.
* <div id="BasicSearchFields"></div>
* <div class="ToggleAdvancedSearchFields" style="display:none"><a href="#">Show advanced options</a></div>
* <div id="AdvancedSearchFields"></div>
*
* @return FieldSet
*/
abstract function getSearchFields();
/**
* Provide custom link.
*
* @return String
*/
abstract function getLink();
//abstract function create();
/**
* Legacy
*/
function AddForm() {
return $this->CreationForm();
}
}
?>

66
code/ImprintStats.php Executable file
View File

@ -0,0 +1,66 @@
<?php
class ImprintStats extends ViewableData {
protected static $imprintID;
static function setID($id) {
ImprintStats::$imprintID = $id;
}
function forTemplate() {
$page = $this->CurrentPage();
if($page){
$description = str_replace(array("\n","\r"),"",$page->MetaDescription);
$title = Convert::raw2js($page->Title);
$level1 = $page->Level(1);
$level2 = $page->Level(2);
if($level1->URLSegment == 'modes') {
$level1 = $level2;
$level2 = $page->Level(3);
}
if($level2 && $level1->URLSegment == 'home') {
$level1 = $level2;
$level2 = $page->Level(3);
}
$section = $level1->Title;
$service = $level2 ? $level2->Title : $section;
$fullURL = $page->Link();
$imprintID = ImprintStats::$imprintID;
return "<!-- Start: Nexsys Imprint Code (Copyright Nexsys Development Ltd 2002-2005) -->
<script src=\"cms/javascript/ImprintStats.js\" type=\"text/javascript\"></script>
<script type=\"text/javascript\">
//<![CDATA[
var NI_DESCRIPTION = \"$description\";
var NI_SECTION = \"$section\";
var NI_SERVICE = \"$service\";
var NI_TRIGGER = \"$trigger\";
var NI_AMOUNT = \"0\";
var NI_ADCAMPAIGN = \"\";
var NI_TITLE = \"$title\";
var NI_URL = \"$fullURL\";
var NI_BASKET_ADD = \"\";
var NI_BASKET_REMOVE = \"\";
var NI_PARAMETERS = \"\";
if (typeof(NI_IW) != \"undefined\") ni_TrackHit(\"imprint1.nexsysdev.net\", $imprintID, NI_DESCRIPTION, NI_SECTION, NI_SERVICE, NI_TRIGGER, NI_AMOUNT, NI_ADCAMPAIGN, NI_TITLE, NI_URL, 1, NI_BASKET_ADD, NI_BASKET_REMOVE, NI_PARAMETERS);
else document.write('<div style=\"position:absolute;width:1px;height:1px;overflow:hidden\"><img src=\"http://imprint1.nexsysdev.net/Hit.aspx?tv=1&sc=$imprintID&js=1\" width=\"1\" height=\"1\" style=\"border:0\" alt=\"Nexsys Imprint\"/></div>');
//]]>
</script>
<noscript>
<div style=\"position:absolute;width:1px;height:1px;overflow:hidden\"><a href=\"http://www.nexsysdev.com\"><img src=\"http://imprint1.nexsysdev.net/Hit.aspx?tv=1&sc=55075&lo=&st=&sv=&tr=&ac=&js=0\" width=\"1\" height=\"1\" alt=\"Nexsys Imprint\" /></a></div>
</noscript>
<!-- End: Nexsys Imprint Code -->";
}
}
}
?>

761
code/LeftAndMain.php Normal file
View File

@ -0,0 +1,761 @@
<?php
/**
* LeftAndMain is the parent class of all the two-pane views in the CMS.
* If you are wanting to add more areas to the CMS, you can do it by subclassing LeftAndMain.
*/
abstract class LeftAndMain extends Controller {
static $tree_class = null;
static $extra_menu_items = array(), $removed_menu_items = array(), $replaced_menu_items = array();
static $ForceReload;
function init() {
Director::set_site_mode('cms');
parent::init();
// Security check for LeftAndMain sub-class permissions
if(!Permission::check("CMS_ACCESS_$this->class")) {
// When access /admin/, we should try a redirect to another part of the admin rather than a
if($this->class == 'CMSMain') {
$menu = $this->MainMenu();
if(($first = $menu->First()) && $first->Link) {
Director::redirect($first->Link);
exit();
}
}
$messageSet = array(
'default' => "Enter your email address and password to access the CMS.",
'alreadyLoggedIn' => "I'm sorry, but you can't access that part of the CMS. If you want to log in as someone else, do so below",
'logInAgain' => "You have been logged out of the CMS. If you would like to log in again, enter a username and password below.",
);
Security::permissionFailure($this, $messageSet);
exit;
}
Requirements::javascript("jsparty/prototype.js");
Requirements::javascript("jsparty/behaviour.js");
Requirements::javascript("jsparty/prototype_improvements.js");
Requirements::javascript("jsparty/loader.js");
Requirements::javascript("jsparty/layout_helpers.js");
Requirements::javascript("jsparty/tree/tree.js");
Requirements::css("jsparty/tree/tree.css");
Requirements::javascript("jsparty/scriptaculous/effects.js");
Requirements::javascript("jsparty/scriptaculous/dragdrop.js");
Requirements::javascript("jsparty/tabstrip/tabstrip.js");
Requirements::css("jsparty/tabstrip/tabstrip.css");
Requirements::css("jsparty/greybox/greybox.css");
Requirements::javascript("jsparty/greybox/AmiJS.js");
Requirements::javascript("jsparty/greybox/greybox.js");
Requirements::javascript("cms/javascript/LeftAndMain.js");
Requirements::javascript("cms/javascript/LeftAndMain_left.js");
Requirements::javascript("cms/javascript/LeftAndMain_right.js");
Requirements::javascript("jsparty/calendar/calendar.js");
Requirements::javascript("jsparty/calendar/lang/calendar-en.js");
Requirements::javascript("jsparty/calendar/calendar-setup.js");
Requirements::css("sapphire/css/CalendarDateField.css");
Requirements::css("jsparty/calendar/calendar-win2k-1.css");
Requirements::javascript('sapphire/javascript/Validator.js');
Requirements::css("sapphire/css/SubmittedFormReportField.css");
}
/**
* Returns true if the current user can access the CMS
*/
function canAccessCMS() {
$member = Member::currentUser();
if($member) {
if($groups = $member->Groups()) {
foreach($groups as $group) if($group->CanCMS) return true;
}
}
return false;
}
/**
* Returns true if the current user has administrative rights in the CMS
*/
function canAdminCMS() {
if($member = Member::currentUser()) return $member->isAdmin();
}
//------------------------------------------------------------------------------------------//
// Main controllers
/**
* You should implement a Link() function in your subclass of LeftAndMain,
* to point to the URL of that particular controller.
*/
abstract public function Link();
public function show($params) {
if($params['ID']) $this->setCurrentPageID($params['ID']);
if($params['OtherID'])
Session::set('currentMember', $params['OtherID']);
if(Director::is_ajax()) {
SSViewer::setOption('rewriteHashlinks', false);
return $this->EditForm()->formHtmlContent();
} else {
return array();
}
}
public function getitem() {
$this->setCurrentPageID($_REQUEST['ID']);
SSViewer::setOption('rewriteHashlinks', false);
// Changed 3/11/2006 to not use getLastFormIn because that didn't have _form_action, _form_name, etc.
$form = $this->EditForm();
if($form) return $form->formHtmlContent();
else return "";
}
public function getLastFormIn($html) {
$parts = split('</?form[^>]*>', $html);
return $parts[sizeof($parts)-2];
}
//------------------------------------------------------------------------------------------//
// Main UI components
/**
* Returns the main menu of the CMS. This is also used by init() to work out which sections the user
* has access to.
*/
public function MainMenu() {
// Don't accidentally return a menu if you're not logged in - it's used to determine access.
if(!Member::currentUserID()) return new DataObjectSet();
// Built-in modules
// array[0]: Name of the icon
// array[1]: URL to visi
// array[2]: The controller class for this menu, used to check permisssions. If blank, it's assumed that this is public, and always shown to
// users who have the rights to access some other part of the admin area.
$menuSrc = array(
"Site Content" => array("content", "admin/", "CMSMain"),
"Files & Images" => array("files", "admin/assets/", "AssetAdmin"),
"Newsletters" => array("newsletter", "admin/newsletter/", "NewsletterAdmin"),
"Reports" => array("report", "admin/reports/", "ReportAdmin"),
"Security" => array("security", "admin/security/", "SecurityAdmin"),
"Help" => array("help", "http://userhelp.silverstripe.com"),
);
if(!$this->hasReports()) unset($menuSrc['Reports']);
// Extra modules
if($removed = $this->stat('removed_menu_items')) {
foreach($removed as $remove) {
foreach($menuSrc as $k => $v) {
if($v[0] == $remove) {
unset($menuSrc[$k]);
break;
}
}
}
}
// replace menu items
if($replaced = $this->stat('replaced_menu_items') ) {
$newMenu = array();
reset( $menuSrc );
for( $i = 0; $i < count( $menuSrc ); $i++ ) {
$existing = current($menuSrc);
if( $replacement = $replaced[$existing[0]] ) {
$newMenu = array_merge( $newMenu, $replacement );
} else
$newMenu = array_merge( $newMenu, array( key( $menuSrc) => current( $menuSrc ) ) );
next( $menuSrc );
}
reset( $menuSrc );
$menuSrc = $newMenu;
}
// Extra modules
if($extra = $this->stat('extra_menu_items')) {
foreach($extra as $k => $v) {
if(!is_array($v)) $extra[$k] = array($k, $v, 'title' => $k);
else $extra[$k]['title'] = $k;
}
array_splice($menuSrc, count($menuSrc)-2, 0, $extra);
}
// Encode into DO set
$menu = new DataObjectSet();
$itemsWithPermission = 0;
foreach($menuSrc as $title => $menuItem) {
if(is_numeric($title) && isset($menuItem['title'])) $title = $menuItem['title'];
if(!isset($menuItem[2]) || Permission::check("CMS_ACCESS_$menuItem[2]")) {
// Count up the number of items that have specific permission settings
if(isset($menuItem[2])) $itemsWithPermission++;
$linkingmode = "";
if(!(strpos($this->Link(), $menuItem[1]) === false)) {
if($menuItem[0] == "content") {
if($this->Link() == "admin/")
$linkingmode = "current";
}
else
$linkingmode = "current";
}
$menu->push(new ArrayData(array(
"Title" => Convert::raw2xml($title),
"Code" => $menuItem[0],
"Link" => $menuItem[1],
"LinkingMode" => $linkingmode
)));
}
}
// Only return a menu if there is at least one permission-requiring item. Otherwise, the menu will just be the "help" icon.
if($itemsWithPermission > 0) return $menu;
else return new DataObjectSet();
}
/**
* Return a list of appropriate templates for this class, with the given suffix
*/
protected function getTemplatesWithSuffix($suffix) {
$classes = array_reverse(ClassInfo::ancestry($this->class));
foreach($classes as $class) {
$templates[] = $class . $suffix;
if($class == 'LeftAndMain') break;
}
return $templates;
}
public function Left() {
return $this->renderWith($this->getTemplatesWithSuffix('_left'));
}
public function Right() {
return $this->renderWith($this->getTemplatesWithSuffix('_right'));
}
public function RightBottom() {
if(SSViewer::hasTemplate($this->getTemplatesWithSuffix('_rightbottom'))) {
return $this->renderWith($this->getTemplatesWithSuffix('_rightbottom'));
}
}
public function getRecord($id, $className = null) {
if(!$className) $className = $this->stat('tree_class');
return DataObject::get_by_id($className, $rootID);
}
function getSiteTreeFor($className, $rootID = null) {
$obj = $rootID ? $this->getRecord($rootID) : singleton($className);
$obj->markPartialTree();
if($p = $this->currentPage()) $obj->markToExpose($p);
// getChildrenAsUL is a flexible and complex way of traversing the tree
$siteTree = $obj->getChildrenAsUL("", '
"<li id=\"record-$child->ID\" class=\"" . $child->CMSTreeClasses($extraArg) . "\">" .
"<a href=\"" . Director::link(substr($extraArg->Link(),0,-1), "show", $child->ID) . "\" " . (($child->canEdit() || $child->canAddChildren()) ? "" : "class=\"disabled\"") . ">" .
($child->TreeTitle()) .
"</a>"
'
,$this, true);
// Wrap the root if needs be.
if(!$rootID) {
$rootLink = $this->Link() . '0';
$siteTree = "<ul id=\"sitetree\" class=\"tree unformatted\"><li id=\"record-0\" class=\"Root nodelete\"><a href=\"$rootLink\">Site Content</a>"
. $siteTree . "</li></ul>";
}
return $siteTree;
}
public function getsubtree() {
$results = $this->getSiteTreeFor($this->stat('tree_class'), $_REQUEST['ID']);
return substr(trim($results), 4,-5);
}
/**
* Allows you to returns a new data object to the tree (subclass of sitetree)
* and updates the tree via javascript.
*/
public function returnItemToUser($p) {
if(Director::is_ajax()) {
// Prepare the object for insertion.
$parentID = (int)$p->ParentID;
$id = $p->ID ? $p->ID : "new-$p->class-$p->ParentID";
$treeTitle = Convert::raw2js($p->TreeTitle());
$hasChildren = is_numeric( $id ) && $p->AllChildren() ? ' unexpanded' : '';
// Ensure there is definitly a node avaliable. if not, append to the home tree.
$response = <<<JS
var tree = $('sitetree');
var newNode = tree.createTreeNode("$id", "$treeTitle", "{$p->class}{$hasChildren}");
node = tree.getTreeNodeByIdx($parentID);
if(!node){ node = tree.getTreeNodeByIdx(0); }
node.open();
node.appendTreeNode(newNode);
newNode.selectTreeNode();
JS;
FormResponse::add($response);
}
return FormResponse::respond();
}
/**
* Save page handler
*/
public function save($urlParams, $form) {
$className = $this->stat('tree_class');
$result = '';
$id = $_REQUEST['ID'];
if(substr($id,0,3) != 'new') {
$record = DataObject::get_one($className, "`$className`.ID = $id");
} else {
$record = $this->getNewItem($id, false);
}
// We don't want to save a new version if there are no changes
$dataFields_new = $form->Fields()->dataFields();
$dataFields_old = $record->getAllFields();
$changed = false;
$hasNonRecordFields = false;
foreach($dataFields_new as $datafield) {
// if the form has fields not belonging to the record
if(!isset($dataFields_old[$datafield->Name()])) {
$hasNonRecordFields = true;
}
// if field-values have changed
if(!isset($dataFields_old[$datafield->Name()]) || $dataFields_old[$datafield->Name()] != $datafield->dataValue()) {
$changed = true;
}
}
if(!$changed && !$hasNonRecordFields) {
// Tell the user we have saved even though we haven't, as not to confuse them
if(is_a($record, "Page")) {
$record->Status = "Saved (update)";
}
FormResponse::status_message("Saved", "good");
FormResponse::update_status($record->Status);
return FormResponse::respond();
}
$form->dataFieldByName('ID')->Value = 0;
if(isset($urlParams['Sort']) && is_numeric($urlParams['Sort'])) {
$record->Sort = $urlParams['Sort'];
}
// HACK: This should be turned into something more general
$originalClass = $record->ClassName;
$originalStatus = $record->Status;
$record->HasBrokenLink = 0;
$record->HasBrokenFile = 0;
$record->writeWithoutVersion();
// HACK: This should be turned into something more general
$originalURLSegment = $record->URLSegment;
$form->saveInto($record, true);
if(is_a($record, "Page")) {
$record->Status = ($record->Status == "New page" || $record->Status == "Saved (new)") ? "Saved (new)" : "Saved (update)";
}
// $record->write();
if(Director::is_ajax()) {
if($id != $record->ID) {
FormResponse::add("$('sitetree').setNodeIdx(\"$id\", \"$record->ID\");");
FormResponse::add("$('Form_EditForm').elements.ID.value = \"$record->ID\";");
}
$title = Convert::raw2js($record->TreeTitle());
if($added = DataObjectLog::getAdded('SiteTree')) {
foreach($added as $page) {
if($page->ID != $record->ID) $result .= $this->addTreeNodeJS($page);
}
}
if($deleted = DataObjectLog::getDeleted('SiteTree')) {
foreach($deleted as $page) {
if($page->ID != $record->ID) $result .= $this->deleteTreeNodeJS($page);
}
}
if($changed = DataObjectLog::getChanged('SiteTree')) {
foreach($changed as $page) {
if($page->ID != $record->ID) {
$title = Convert::raw2js($page->TreeTitle());
FormResponse::add("$('sitetree').setNodeTitle($page->ID, \"$title\")");
}
}
}
FormResponse::add("$('sitetree').setNodeTitle(\"$record->ID\", \"$title\");");
$message = "Saved.";
// Update the icon if the class has changed
if($originalClass != $record->ClassName) {
$record->setClassName( $record->ClassName );
$newClass = $record->ClassName;
$record = $record->newClassInstance( $newClass );
FormResponse::add("if(\$('sitetree').setNodeIcon) \$('sitetree').setNodeIcon($record->ID, '$originalClass', '$record->ClassName');");
}
// HACK: This should be turned into somethign more general
if( ($record->class == 'VirtualPage' && $originalURLSegment != $record->URLSegment) ||
($originalClass != $record->ClassName) || self::$ForceReload == true) {
FormResponse::add("$('Form_EditForm').getPageFromServer($record->ID);");
}
if( ($record->class != 'VirtualPage') && $originalURLSegment != $record->URLSegment) {
$message .= " Changed URL to '$record->URLSegment'";
FormResponse::add("\$('Form_EditForm').elements.URLSegment.value = \"$record->URLSegment\";");
FormResponse::add("\$('Form_EditForm_StageURLSegment').value = \"{$record->URLSegment}\";");
}
// After reloading action
if($originalStatus != $record->Status) {
$message .= " Status changed to '$record->Status'";
}
$record->write();
$result .= $this->getActionUpdateJS($record);
FormResponse::status_message($message, "good");
FormResponse::update_status($record->Status);
}
return FormResponse::respond();
}
/**
* Return a piece of javascript that will update the actions of the main form
*/
public function getActionUpdateJS($record) {
// Get the new action buttons
$tempForm = $this->getEditForm($record->ID);
$actionList = '';
foreach($tempForm->Actions() as $action) {
$actionList .= $action->Field() . ' ';
}
FormResponse::add("$('Form_EditForm').loadActionsFromString('" . Convert::raw2js($actionList) . "');");
return FormResponse::respond();
}
/**
* Return JavaScript code to generate a tree node for the given page, if visible
*/
public function addTreeNodeJS($page, $select = false) {
$parentID = (int)$page->ParentID;
$title = Convert::raw2js($page->TreeTitle());
$response = <<<JS
var newNode = $('sitetree').createTreeNode($page->ID, \"$title\", \"$page->class\");
var parentNode = $('sitetree').getTreeNodeByIdx($parentID);
if(parentNode) parentNode.appendTreeNode(newNode);\n"
($selected ? "newNode.selectTreeNode();\n" : "") ;
JS;
FormResponse::add($response);
return FormResponse::respond();
}
/**
* Return JavaScript code to remove a tree node for the given page, if it exists.
*/
public function deleteTreeNodeJS($page) {
$id = $page->ID ? $page->ID : $page->OldID;
$response = <<<JS
var node = $('sitetree').getTreeNodeByIdx($id);
if(node && node.parentTreeNode) node.parentTreeNode.removeTreeNode(node);
$('Form_EditForm').closeIfSetTo($id);
JS;
FormResponse::add($response);
return FormResponse::respond();
}
/**
* Sets a static variable on this class which means the panel will be reloaded.
*/
static function ForceReload(){
self::$ForceReload = true;
}
/**
* Ajax handler for updating the parent of a tree node
*/
public function ajaxupdateparent() {
$id = $_REQUEST['ID'];
$parentID = $_REQUEST['ParentID'];
if($parentID == 'root'){
$parentID = 0;
}
$_REQUEST['ajax'] = 1;
if(is_numeric($id) && is_numeric($parentID) && $id != $parentID) {
$node = DataObject::get_by_id($this->stat('tree_class'), $id);
if($node){
$node->ParentID = $parentID;
$node->Status = "Saved (update)";
$node->write();
if(is_numeric($_REQUEST['CurrentlyOpenPageID'])) {
$currentPage = DataObject::get_by_id($this->stat('tree_class'), $_REQUEST['CurrentlyOpenPageID']);
if($currentPage) {
$cleanupJS = $currentPage->cmsCleanup_parentChanged();
}
}
FormResponse::status_message('saved', 'good');
FormResponse::add($cleanupJS);
}else{
FormResponse::status_message("Please Save Page: This page could not be upated because it hasn't been saved yet.","good");
}
return FormResponse::respond();
} else {
user_error("Error in ajaxupdateparent request; id=$id, parentID=$parentID", E_USER_ERROR);
}
}
/**
* Ajax handler for updating the order of a number of tree nodes
* $_GET[ID]: An array of node ids in the correct order
* $_GET[MovedNodeID]: The node that actually got moved
*/
public function ajaxupdatesort() {
$className = $this->stat('tree_class');
$counter = 0;
$js = '';
$_REQUEST['ajax'] = 1;
if(is_array($_REQUEST['ID'])) {
$movedNode = DataObject::get_by_id($className, $_REQUEST['MovedNodeID']);
foreach($_REQUEST['ID'] as $id) {
if($id == $movedNode->ID) {
$movedNode->Sort = ++$counter;
$movedNode->Status = "Saved (update)";
$movedNode->write();
$title = Convert::raw2js($movedNode->TreeTitle());
$js .="$('sitetree').setNodeTitle($movedNode->ID, \"$title\")\n";
// Nodes that weren't "actually moved" shouldn't be registered as having been edited; do a direct SQL update instead
} else if(is_numeric($id)) {
++$counter;
DB::query("UPDATE `$className` SET `Sort` = $counter WHERE `ID` = '$id'");
}
}
// Virtual pages require selected to be null if the page is the same.
FormResponse::add(
"if( $('sitetree').selected && $('sitetree').selected[0]){
var idx = $('sitetree').selected[0].getIdx();
if(idx){
$('Form_EditForm').getPageFromServer(idx);
}
}\n" . $js
);
FormResponse::status_message('saved', 'good');
} else {
FormResponse::error("Error in request");
}
return FormResponse::respond();
}
/**
* Delete a number of items
*/
public function deleteitems() {
$ids = split(' *, *', $_REQUEST['csvIDs']);
$script = "st = \$('sitetree'); \n";
foreach($ids as $id) {
if(is_numeric($id)) {
DataObject::delete_by_id($this->stat('tree_class'), $id);
$script .= "node = st.getTreeNodeByIdx($id); if(node) node.parentTreeNode.removeTreeNode(node); $('Form_EditForm').closeIfSetTo($id); \n";
}
}
FormResponse::add($script);
return FormResponse::respond();
}
public function EditForm() {
$id = isset($_REQUEST['ID']) ? $_REQUEST['ID'] : $this->currentPageID();
if($id) return $this->getEditForm($id);
}
public function printable() {
$id = $_REQUEST['ID'] ? $_REQUEST['ID'] : $this->currentPageID();
if($id) $form = $this->getEditForm($id);
$form->transform(new PrintableTransformation());
$form->actions = null;
Requirements::clear();
Requirements::css('cms/css/LeftAndMain_printable.css');
return array(
"PrintForm" => $form
);
}
public function currentPageID() {
if(isset($_REQUEST['ID']) && is_numeric($_REQUEST['ID'])) {
return $_REQUEST['ID'];
} elseif (is_numeric($this->urlParams['ID'])) {
return $this->urlParams['ID'];
} elseif(Session::get("{$this->class}.currentPage")) {
return Session::get("{$this->class}.currentPage");
} else {
return null;
}
}
public function setCurrentPageID($id) {
Session::set("{$this->class}.currentPage", $id);
}
public function currentPage() {
$id = $this->currentPageID();
if($id && is_numeric($id)) {
return DataObject::get_by_id($this->stat('tree_class'), $id);
}
}
public function isCurrentPage(DataObject $page) {
return $page->ID == Session::get("{$this->class}.currentPage");
}
/**
* Return the CMS's HTML-editor toolbar
*/
public function EditorToolbar() {
return new HtmlEditorField_Toolbar($this, "EditorToolbar");
}
/**
* Return the version number of this application
*/
public function CMSVersion() {
$sapphireVersionFile = file_get_contents('../sapphire/silverstripe_version');
$jspartyVersionFile = file_get_contents('../jsparty/silverstripe_version');
$cmsVersionFile = file_get_contents('../cms/silverstripe_version');
if(strstr($sapphireVersionFile, "/sapphire/trunk")) {
$sapphireVersion = "trunk";
} else {
preg_match("/sapphire\/(?:(?:branches)|(?:tags))(?:\/rc)?\/([A-Za-z0-9._-]+)\/silverstripe_version/", $sapphireVersionFile, $matches);
$sapphireVersion = $matches[1];
}
if(strstr($jspartyVersionFile, "/jsparty/trunk")) {
$jspartyVersion = "trunk";
} else {
preg_match("/jsparty\/(?:(?:branches)|(?:tags))(?:\/rc)?\/([A-Za-z0-9._-]+)\/silverstripe_version/", $jspartyVersionFile, $matches);
$jspartyVersion = $matches[1];
}
if(strstr($cmsVersionFile, "/cms/trunk")) {
$cmsVersion = "trunk";
} else {
preg_match("/cms\/(?:(?:branches)|(?:tags))(?:\/rc)?\/([A-Za-z0-9._-]+)\/silverstripe_version/", $cmsVersionFile, $matches);
$cmsVersion = $matches[1];
}
if($sapphireVersion == $jspartyVersion && $jspartyVersion == $cmsVersion) {
return $sapphireVersion;
} else {
return "cms: $cmsVersion, sapphire: $sapphireVersion, jsparty: $jspartyVersion";
}
}
/**
* The application name is customisable by calling
* LeftAndMain::setApplicationName("Something New")
*/
static $application_name = "SilverStripe CMS", $application_logo_text = "SilverStripe CMS";
static function setApplicationName($name, $logoText = null) {
self::$application_name = $name;
self::$application_logo_text = $logoText ? $logoText : $name;
}
function ApplicationName() {
return self::$application_name;
}
function ApplicationLogoText() {
return self::$application_logo_text;
}
static $application_logo = "cms/images/mainmenu/logo.gif", $application_logo_style = "";
static function setLogo($logo, $logoStyle) {
self::$application_logo = $logo;
self::$application_logo_style = $logoStyle;
self::$application_logo_text = "";
}
function LogoStyle() {
return "background-image: url(" . self::$application_logo . "); " . self::$application_logo_style;
}
/**
* Determine if we have reports and need to display the Reports main menu item
*
* @return boolean
*/
function hasReports() {
$subclasses = ClassInfo::subclassesFor('Report');
foreach($subclasses as $class){
if($class != 'Report') {
return true;
}
}
return false;
}
/**
* Return the base directory of the tiny_mce codebase
*/
function MceRoot() {
return MCE_ROOT;
}
}
?>

271
code/MemberList.php Executable file
View File

@ -0,0 +1,271 @@
<?php
class MemberList extends FormField {
protected $members;
protected $hidePassword;
protected $pageSize;
function __construct($name, $group, $members = null, $hidePassword = null, $pageLimit = 10) {
if($group) {
if(is_object($group)) $this->group = $group;
else $this->group = DataObject::get_by_id('Group',$group);
}
$this->pageSize = $pageLimit;
if($members) $this->members = $this->memberListWithGroupID($members, $group);
$this->hidePassword = $hidePassword;
parent::__construct(null);
}
function memberListWithGroupID($members, $group) {
$newMembers = new DataObjectSet();
foreach($members as $member) {
$newMembers->push($member->customise(array("GroupID" => $group->ID)));
}
return $newMembers;
}
function FieldHolder() {
return $this->renderWith("MemberList");
}
function setGroup($group) {
$this->group = $group;
}
function setController($controller) {
$this->controller = $controller;
}
function GroupID() {
if( $this->group )
return $this->group->ID;
return '0';
}
function GetControllerName() {
return $this->controller->class;
}
function Members() {
/*if($this->members)
$members = $this->members;
else if($this->group) {
$members = $this->group->Members( $this->pageSize, 0 );
$allMembers = $this->group->Members();
if( $allMembers )
$total = $allMembers->Count();
} else
return null;*/
if( $this->members )
return $this->members;
if( !$baseGroup )
$baseGroup = $this->group->ID;
// Debug::message( $_REQUEST['MemberListOrderByField'] );
// construct the filter and sort
if( $_REQUEST['MemberListOrderByField'] )
$sort = "`" . $_REQUEST['MemberListOrderByField'] . "`" . addslashes( $_REQUEST['MemberListOrderByOrder'] );
$whereClauses = array();
$search = addslashes( $_REQUEST['MemberListSearch'] );
if( is_numeric( $_REQUEST['MemberListStart'] ) )
$limitClause = ( $_REQUEST['MemberListStart'] ) . ", {$this->pageSize}";
else
$limitClause = "0, {$this->pageSize}";
if( !empty($_REQUEST['MemberListSearch']) )
$whereClauses[] = "( `Email`='$search' OR `FirstName`='$search' OR `Surname`='$search' )";
if( is_numeric( $_REQUEST['MemberListBaseGroup'] ) )
$baseGroup = $_REQUEST['MemberListBaseGroup'];
$whereClauses[] = "`GroupID`='".$baseGroup."'";
$join = "INNER JOIN `Group_Members` ON `MemberID`=`Member`.`ID`";
// $_REQUEST['showqueries'] = 1;
$members = DataObject::get('Member', implode( ' AND ', $whereClauses ), $sort, $join, $limitClause );
// $_REQUEST['showqueries'] = 0;
if( is_numeric( $_REQUEST['MemberListGroup'] ) ) {
$baseMembers = new DataObjectSet();
if( $members )
foreach( $members as $member )
if( $member->inGroup( $_REQUEST['MemberListGroup'] ) )
$baseMembers->push( $member );
} else
$baseMembers = $members;
if($members){
$members->setPageLimits( $_REQUEST['MemberListStart'], $this->pageSize, $total );
$this->members = $this->memberListWithGroupID($members, $this->group);
}
return $this->members;
}
function FirstLink() {
if( !$_REQUEST['MemberListStart'] )
return null;
return "admin/security/listmembers?MemberListStart=0{$this->filterString()}";
}
function filterString() {
foreach( $_REQUEST as $key => $value ) {
if( strpos( $key, 'MemberList' ) === 0 && $key != 'MemberListStart' )
$filterString .= "&$key=$value";
}
if( !$_REQUEST['MemberListBaseGroup'] )
$filterString .= '&MemberListBaseGroup=' . $this->group->ID;
return $filterString;
}
function MemberListStart() {
return $_REQUEST['MemberListStart'];
}
function PrevLink() {
if( !$_REQUEST['MemberListStart'] )
return null;
$prevStart = $_REQUEST['MemberListStart'] - $this->pageSize;
if( $prevStart < 0 )
$prevStart = 0;
return "admin/security/listmembers?MemberListStart=$prevStart{$this->filterString()}";
}
function NextLink() {
$total = $this->TotalMembers();
$lastStart = $total - ( $total % $this->pageSize );
if( $_REQUEST['MemberListStart'] >= $lastStart )
return null;
return "admin/security/listmembers?MemberListStart={$this->pageSize}{$this->filterString()}";
}
function LastLink() {
$total = $this->TotalMembers();
$lastStart = $total - ( $total % $this->pageSize );
if( $_REQUEST['MemberListStart'] >= $lastStart )
return null;
return "admin/security/listmembers?MemberListStart=$lastStart{$this->filterString()}";
}
function PageSize() {
return $this->pageSize;
}
function FirstMember() {
return $_REQUEST['MemberListStart'] + 1;
}
function LastMember() {
return $_REQUEST['MemberListStart'] + min( $_REQUEST['MemberListStart'] + $this->pageSize, $this->TotalMembers() - $_REQUEST['MemberListStart'] );
}
function TotalMembers() {
if($this->group) $members = $this->group->Members();
if( !$members )
return null;
return $members->TotalItems();
}
function DontShowPassword(){
if( $this->hidePassword )
return true;
return $this->controller->class=='CMSMain'||$this->controller->class=='NewsletterAdmin';
}
function AddRecordForm() {
if($this->DontShowPassword())
{
return new TabularStyle(new Form($this->controller,'AddRecordForm',
new FieldSet(
new TextField("FirstName", "First Name"),
new TextField("Surname", "Surname"),
new TextField("Email", "Email"),
new HiddenField("GroupID", null, $this->group->ID)
),
new FieldSet(
new FormAction("addmember", "Add")
)
));
} else {
return new TabularStyle(new Form($this->controller,'AddRecordForm',
new FieldSet(
new TextField("FirstName", "First Name"),
new TextField("Surname", "Surname"),
new TextField("Email", "Email"),
new TextField("Password", "Password"),
new HiddenField("GroupID", null, $this->group->ID)
),
new FieldSet(
new FormAction("addmember", "Add")
)
));
}
}
function SearchField() {
$field = new TextField( 'MemberListSearch', 'Search' );
return $field->FieldHolder();
}
function OrderByField() {
$fields = new FieldGroup( new DropdownField('MemberListOrderByField','', array(
'FirstName' => 'FirstName',
'Surname' => 'Surname',
'Email' => 'Email'
)),
new DropdownField('MemberListOrderByOrder','',array(
'ASC' => 'Ascending',
'DESC' => 'Descending'
)));
$field = new FieldGroup( new LabelField( 'Order by' ), $fields );
return $field->FieldHolder();
}
function GroupFilter() {
$groups = DataObject::get('Group');
$groupArray = array( '' => 'Any group' );
foreach( $groups as $group )
$groupArray[$group->ID] = $group->Title;
$field = new DropdownField('MemberListGroup','Filter by group',$groupArray );
return $field->FieldHolder();
}
}
?>

412
code/MemberTableField.php Executable file
View File

@ -0,0 +1,412 @@
<?php
/**
* Enhances {ComplexTableField} with the ability to list groups and given members.
* It is based around groups, so it deletes Members from a Group rather than from the entire system.
*
* In contrast to the original implementation, the URL-parameters "ParentClass" and "ParentID" are used
* to specify "Group" (hardcoded) and the GroupID-relation.
*
* Returns either:
* - provided members
* - members of a provided group
* - all members
* - members based on a search-query
*/
class MemberTableField extends ComplexTableField {
protected $members;
protected $hidePassword;
protected $pageSize;
protected $detailFormValidator;
protected $group;
protected $template = "MemberTableField";
static $data_class = "Member";
protected $permissions = array(
"add",
"edit",
"delete"
);
private static $addedPermissions = array();
private static $addedFields = array();
private static $addedCsvFields = array();
public static function addPermissions( $addingPermissionList ) {
self::$addedPermissions = $addingPermissionList;
}
public static function addMembershipFields( $addingFieldList, $addingCsvFieldList = null ) {
self::$addedFields = $addingFieldList;
$addingCsvFieldList == null ? self::$addedCsvFields = $addingFieldList : self::$addedCsvFields = $addingCsvFieldList;
}
function __construct($controller, $name, $group, $members = null, $hidePassword = true, $pageLimit = 10) {
if($group) {
if(is_object($group)) {
$this->group = $group;
} else if(is_numeric($group)){
$this->group = DataObject::get_by_id('Group',$group);
}
} else if(is_numeric($_REQUEST['ctf'][$this->Name()]["ID"])) {
$this->group = DataObject::get_by_id('Group',$_REQUEST['ctf'][$this->Name()]["ID"]);
}
$sourceClass = $this->stat("data_class");
foreach( self::$addedPermissions as $permission )
array_push( $this->permissions, $permission );
$fieldList = array(
"FirstName" => "Firstname",
"Surname" => "Surname",
"Email" => "Email"
);
$csvFieldList = $fieldList;
foreach( self::$addedCsvFields as $key => $value ) {
$csvFieldList[$key] = $value;
}
foreach( self::$addedFields as $key => $value ) {
$fieldList[$key] = $value;
}
if(!$hidePassword) {
$fieldList["Password"] = "Password";
}
if(isset($_REQUEST['ctf']['childID']) && $memberID = $_REQUEST['ctf']['childID']) {
$SNG_member = DataObject::get_by_id($this->stat("data_class"),$_REQUEST['ctf']['childID']);
} else {
$SNG_member = singleton(Object::getCustomClass($this->stat("data_class")));
}
$detailFormFields = $SNG_member->getCMSFields();
$this->detailFormValidator = $SNG_member->getValidator();
$this->pageSize = $pageLimit;
// Legacy: Use setCustomSourceItems() instead.
if($members) {
$this->customSourceItems = $this->memberListWithGroupID($members, $group);
}
$this->hidePassword = $hidePassword;
parent::__construct($controller, $name, $sourceClass, $fieldList, $detailFormFields);
Requirements::javascript("cms/javascript/MemberTableField.js");
// construct the filter and sort
if(isset($_REQUEST['MemberOrderByField'])) {
$this->sourceSort = "`" . Convert::raw2sql($_REQUEST['MemberOrderByField']) . "`" . Convert::raw2sql( $_REQUEST['MemberOrderByOrder'] );
}
// search
$search = isset($_REQUEST['MemberSearch']) ? Convert::raw2sql($_REQUEST['MemberSearch']) : null;
if(!empty($_REQUEST['MemberSearch'])) {
//$this->sourceFilter[] = "( `Email` LIKE '%$search%' OR `FirstName` LIKE '%$search%' OR `Surname` LIKE '%$search%' )";
$sourceF = "( ";
foreach( $fieldList as $k => $v )
$sourceF .= "`$k` LIKE '%$search%' OR ";
$this->sourceFilter[] = substr( $sourceF, 0, -3 ) . ")";
}
// filter by groups
// TODO Not implemented yet
if(isset($_REQUEST['ctf'][$this->Name()]['GroupID']) && is_numeric($_REQUEST['ctf'][$this->Name()]['GroupID'])) {
$this->sourceFilter[] = "`GroupID`='{$_REQUEST['ctf'][$this->Name()]['GroupID']}'";
} elseif($this->group) {
//$this->sourceFilter[] = "`GroupID`='{$this->group->ID}'";
// If the table is not clean (without duplication), the total and navigation wil not work well, so uncheck the big line below
$this->sourceFilter[] = "`Group_Members`.`ID` IN (SELECT `ID` FROM `Group_Members` WHERE `GroupID`='{$this->group->ID}' GROUP BY `MemberID` HAVING MIN(`ID`))";
}
$this->sourceJoin = " INNER JOIN `Group_Members` ON `MemberID`=`Member`.`ID`";
$this->setFieldListCsv( $csvFieldList );
}
/**
* Overridden functions
*/
function sourceID() {
return $this->group->ID;
}
function AddLink() {
return "{$this->PopupBaseLink()}&methodName=add";
}
function DetailForm() {
$ID = Convert::raw2xml($_REQUEST['ctf']['ID']);
$childID = isset($_REQUEST['ctf']['childID']) ? Convert::raw2xml($_REQUEST['ctf']['childID']) : 0;
$childClass = Convert::raw2xml($_REQUEST['fieldName']);
$methodName = isset($_REQUEST['methodName']) ? $_REQUEST['methodName'] : '';
if($methodName == "add") {
$parentIdName = $this->getParentIdName($childClass,$this->getParentClass());
if(!$parentIdName) {
user_error("ComplexTableField::DetailForm() Dataobject does not seem to have an 'has-one'-relationship", E_USER_WARNING);
return;
}
$this->detailFormFields->push(new HiddenField('parentClass'," ",$this->getParentClass()));
}
// the ID field confuses the Controller-logic in finding the right view for ReferencedField
$this->detailFormFields->removeByName('ID');
$this->detailFormFields->push(new HiddenField("ctf[ID]"," ",$ID));
// add a namespaced ID instead thats "converted" by saveComplexTableField()
$this->detailFormFields->push(new HiddenField("ctf[childID]","",$childID));
$this->detailFormFields->push(new HiddenField("ClassName","",$this->sourceClass));
$form = new MemberTableField_Popup($this, "DetailForm", $this->detailFormFields, $this->sourceClass, $methodName == "show", $this->detailFormValidator);
if (is_numeric($childID)) {
if ($methodName == "show" || $methodName == "edit") {
$childData = DataObject::get_by_id($this->sourceClass, $childID);
$form->loadDataFrom($childData);
}
}
if ($methodName == "show") {
$form->makeReadonly();
}
return $form;
}
function SearchForm() {
$searchFields = new FieldGroup(
new TextField('MemberSearch', 'Search'),
new HiddenField("ctf[ID]",'',$this->group->ID),
new HiddenField('MemberFieldName','',$this->name),
new HiddenField('MemberDontShowPassword','',$this->hidePassword)
);
$orderByFields = new FieldGroup(
new LabelField('Order by'),
new FieldSet(
new DropdownField('MemberOrderByField','', array(
'FirstName' => 'FirstName',
'Surname' => 'Surname',
'Email' => 'Email'
)),
new DropdownField('MemberOrderByOrder','',array(
'ASC' => 'Ascending',
'DESC' => 'Descending'
))
)
);
$groups = DataObject::get('Group');
$groupArray = array('' => 'Any group');
foreach( $groups as $group ) {
$groupArray[$group->ID] = $group->Title;
}
$groupFields = new DropdownField('MemberGroup','Filter by group',$groupArray );
$actionFields = new LiteralField('MemberFilterButton','<input type="submit" name="MemberFilterButton" value="Filter" id="MemberFilterButton"/>');
$fieldContainer = new FieldGroup(
$searchFields,
// $orderByFields,
// $groupFields,
$actionFields
);
return $fieldContainer->FieldHolder();
}
/**
* Add existing member to group rather than creating a new member
*/
function addtogroup() {
$data = $_REQUEST;
unset($data['ID']);
if(!is_numeric($data['ctf']['ID'])) {
FormResponse::status_messsage('Adding failed', 'bad');
}
$className = $this->stat('data_class');
$record = new $className();
$record->update($data);
$record->write();
// To Avoid duplication in the Group_Members table if the ComponentSet.php is not modified just uncomment le line below
//if( ! $record->isInGroup( $data['ctf']['ID'] ) )
$record->Groups()->add( $data['ctf']['ID'] );
$this->sourceItems();
// TODO add javascript to highlight added row (problem: might not show up due to sorting/filtering)
FormResponse::update_dom_id($this->id(), $this->renderWith($this->template), true);
FormResponse::status_message('Added member to group', 'good');
return FormResponse::respond();
}
/**
* Custom delete implementation:
* Remove member from group rather than from the database
*/
function delete() {
$groupID = Convert::raw2sql($_REQUEST["ctf"]["ID"]);
$memberID = Convert::raw2sql($_REQUEST["ctf"]["childID"]);
if(is_numeric($groupID) && is_numeric($memberID)) {
$member = DataObject::get_by_id('Member', $memberID);
$member->Groups()->remove($groupID);
} else {
user_error("MemberTableField::delete: Bad parameters: Group=$groupID, Member=$memberID", E_USER_ERROR);
}
return FormResponse::respond();
}
/**
* #################################
* Utility Functions
* #################################
*/
function getParentClass() {
return "Group";
}
function getParentIdName($childClass,$parentClass){
return "GroupID";
}
/**
* #################################
* Custom Functions
* #################################
*/
function memberListWithGroupID($members, $group) {
$newMembers = new DataObjectSet();
foreach($members as $member) {
$newMembers->push($member->customise(array("GroupID" => $group->ID)));
}
return $newMembers;
}
function setGroup($group) {
$this->group = $group;
}
function setController($controller) {
$this->controller = $controller;
}
function GetControllerName() {
return $this->controller->class;
}
/**
* Add existing member to group by name (with JS-autocompletion)
*/
function AddRecordForm() {
$fields = new FieldSet();
foreach($this->FieldList() as $fieldName=>$fieldTitle) {
$fields->push(new TextField($fieldName));
}
$fields->push(new HiddenField("ctf[ID]", null, $this->group->ID));
return new TabularStyle(new Form($this->controller,'AddRecordForm',
$fields,
new FieldSet(
new FormAction("addtogroup", "Add")
)
));
}
/**
* Cached version for getting the appropraite members for this particular group.
*
* This includes getting inherited groups, such as groups under groups.
*/
function sourceItems(){
// Caching.
if($this->sourceItems) {
return $this->sourceItems;
}
// Setup limits
$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}";
}
// We use the group to get the members, as they already have the bulk of the look up functions
$start = isset($_REQUEST['ctf'][$this->Name()]['start']) ? $_REQUEST['ctf'][$this->Name()]['start'] : 0;
$this->sourceItems = $this->group->Members(
$this->pageSize, // limit
$start, // offset
$this->sourceFilter,
$this->sourceSort
);
$this->unpagedSourceItems = $this->group->Members( "", "", $this->sourceFilter, $this->sourceSort );
$this->totalCount = ($this->sourceItems) ? $this->sourceItems->TotalItems() : 0;
return $this->sourceItems;
}
function TotalCount() {
$this->sourceItems(); // Called for its side-effect of setting total count
return $this->totalCount;
}
}
class MemberTableField_Popup extends ComplexTableField_Popup {
function __construct($controller, $name, $fields, $sourceClass, $readonly=false, $validator = null) {
// DO NOT CHANGE THE ORDER OF THESE JS FILES. THESE ARE ONLY REQUIRED FOR THIS INSTANCE !!!11onetwo
parent::__construct($controller, $name, $fields, $sourceClass, $readonly, $validator);
Requirements::javascript("cms/javascript/MemberTableField.js");
Requirements::javascript("cms/javascript/MemberTableField_popup.js");
}
function saveComplexTableField() {
$id = Convert::raw2sql($_REQUEST['ctf']['childID']);
if (is_numeric($id)) {
$childObject = DataObject::get_by_id($this->sourceClass, $id);
} else {
$childObject = new $this->sourceClass();
}
$this->saveInto($childObject);
$childObject->write();
$childObject->Groups()->add($_REQUEST['ctf']['ID']);
// if ajax-call in an iframe, close window by javascript, else redirect to referrer
if(!Director::is_ajax()) {
Director::redirect(substr($_SERVER['REQUEST_URI'],0,strpos($_SERVER['REQUEST_URI'],"?")));
}
}
}
?>

View File

@ -0,0 +1,93 @@
<?php
/**
* Create a process in session which is incremented to calls from the client
*/
class BatchProcess extends Object {
protected $objects;
protected $current;
protected $id;
protected $scriptOutput = true;
function __construct( $collection ) {
$this->current = 0;
if( $collection ) {
if( is_array( $collection ) )
$this->objects = $collection;
elseif( is_a( $collection, 'DataObjectSet' ) ) {
$this->objects = $collection->toArray();
} else
$this->objects = array( $collection );
}
}
function runToCompletion() {
$this->scriptOutput = false;
$this->current = 0;
$ignore = $this->next( count( $this->objects ) );
$this->complete();
}
function getID() {
return $this->id;
}
function next() {
self::addProcess( $this );
return $this->id.':'.$this->current.'/'.count( $this->objects );
}
function start() {
$this->current = 0;
$this->id = self::generateID();
if( !$this->objects || count( $this->objects ) === 0 )
return $this->complete();
return $this->next();
}
function complete() {
self::removeProcess( $this );
}
static function generateID() {
return count(Session::get('BatchProcesses')) + 1;
}
static function addProcess( $process ) {
Session::set('BatchProcesses.' . ($process->getID() - 1), serialize($process));
}
static function removeProcess( $process ) {
Session::clear('BatchProcesses.' . ($process->getID() - 1));
}
}
class BatchProcess_Controller extends Controller {
function next() {
$processID = $this->urlParams['ID'];
if( !$processID ) {
return "ERROR:Could not continue process";
}
$process = unserialize(Session::get('BatchProcesses.' . ($this->urlParams['ID'] - 1)));
if( !$process ) {
return "ERROR:Could not continue process";
}
if( $this->urlParams['Batch'] )
return $process->next( $this->urlParams['Batch'] );
else
return $process->next();
}
}
?>

46
code/Newsletter/BouncedList.php Executable file
View File

@ -0,0 +1,46 @@
<?php
class BouncedList extends FormField {
protected $nlType;
function __construct( $name, $newsletterType ) {
parent::__construct( $name, '', null );
if( is_object( $newsletterType ) )
$this->nlType = $newsletterType;
else
$this->nlType = DataObject::get_by_id( 'NewsletterType', $newsletterType );
}
function setController($controller) {
$this->controller = $controller;
}
function FieldHolder() {
return $this->renderWith( 'NewsletterAdmin_BouncedList' );
}
function Entries() {
$id = $this->nlType->GroupID;
$bounceRecords = DataObject::get( 'Email_BounceRecord', "`GroupID`='$id'", null, "INNER JOIN `Group_Members` USING(`MemberID`)" );
//user_error($id, E_USER_ERROR );
if( !$bounceRecords )
return null;
foreach( $bounceRecords as $bounceRecord ) {
if( $bounceRecord ) {
$bouncedUsers[] = new ArrayData( array(
'Record' => $bounceRecord,
'Member' => DataObject::get_by_id( 'Member', $bounceRecord->MemberID )
));
}
}
return new DataObjectSet( $bouncedUsers );
}
}
?>

99
code/Newsletter/Newsletter.php Executable file
View File

@ -0,0 +1,99 @@
<?php
class Newsletter extends DataObject {
/**
* Returns a FieldSet with which to create the CMS editing form.
* You can use the extend() method of FieldSet to create customised forms for your other
* data objects.
*/
function getCMSFields($controller = null) {
require_once("forms/Form.php");
$group = DataObject::get_by_id("Group", $this->Parent()->GroupID);
$ret = new FieldSet(
new TabSet("Root",
$mailTab = new Tab("Mail",
new TextField("Subject", "Subject", $this->Subject),
new HtmlEditorField("Content", "Content")
)
)
);
if( $this->Status != 'Draft' ) {
$mailTab->push( new ReadonlyField("SendDate", "Sent at", $this->SendDate) );
}
return $ret;
}
function getTitle() {
return $this->getField('Subject');
}
function getNewsletterType() {
return DataObject::get_by_id('NewsletterType', $this->ParentID);
}
static $db = array(
"Status" => "Enum('Draft, Send', 'Draft')",
"Content" => "HTMLText",
"Subject" => "Varchar(255)",
"SentDate" => "Datetime",
);
static $has_one = array(
"Parent" => "NewsletterType",
);
static $has_many = array(
"Recipients" => "Newsletter_Recipient",
);
static function newDraft( $parentID, $subject, $content ) {
if( is_numeric( $parentID ) ) {
$newsletter = new Newsletter();
$newsletter->Status = 'Draft';
$newsletter->Title = $newsletter->Subject = $subject;
$newsletter->ParentID = $parentID;
$newsletter->Content = $content;
$newsletter->write();
} else {
user_error( $parentID, E_USER_ERROR );
}
return $newsletter;
}
}
class Newsletter_Recipient extends DataObject {
static $db = array(
"ParentID" => "Int",
);
static $has_one = array(
"Member" => "Member",
);
}
class Newsletter_Email extends Email_Template {
protected $nlType;
function __construct($nlType) {
$this->nlType = $nlType;
parent::__construct();
}
function setTemplate( $template ) {
$this->ss_template = $template;
}
function UnsubscribeLink(){
$emailAddr = $this->To();
$nlTypeID = $this->nlType->ID;
return Director::absoluteBaseURL()."unsubscribe/$emailAddr/$nlTypeID";
}
}
?>

View File

@ -0,0 +1,97 @@
<?php
class NewsletterEmailProcess extends BatchProcess {
protected $subject;
protected $body;
protected $from;
protected $newsletter;
protected $nlType;
protected $messageID;
function __construct( $subject, $body, $from, $newsletter, $nlType, $messageID = null ) {
$this->subject = $subject;
$this->body = $body;
$this->from = $from;
$this->newsletter = $newsletter;
$this->nlType = $nlType;
$this->messageID = $messageID;
$groupID = $nlType->GroupID;
parent::__construct( DataObject::get( 'Member', "`GroupID`='$groupID'", null, "INNER JOIN `Group_Members` ON `MemberID`=`Member`.`ID`" ) );
}
function next( $count = 10 ) {
$max = $this->current + $count;
$max = $max < count( $this->objects ) ? $max : count( $this->objects );
while($this->current < $max) {
$index = $this->current++;
$member = $this->objects[$index];
// check to see if the user has unsubscribed from the mailing list
// TODO Join in the above query first
$unsubscribeRecord = DataObject::get_one('Member_UnsubscribeRecord', "`MemberID`='{$member->ID}' AND `NewsletterTypeID`='{$this->nlType->ID}'");
if( !$unsubscribeRecord ) {
$address = $member->Email;
/**
* Email Blacklisting Support
*/
if($member->BlacklistedEmail && Email_BlackList::isBlocked($this->to)){
$bounceRecord = new Email_BounceRecord();
$bounceRecord->BounceEmail = $member->Email;
$bounceRecord->BounceTime = date("Y-m-d H:i:s",time());
$bounceRecord->BounceMessage = "BlackListed Email";
$bounceRecord->MemberID = $member->ID;
$bounceRecord->write();
continue;
}
$e = new Newsletter_Email($this->nlType);
$e->setBody( $this->body );
$e->setSubject( $this->subject );
$e->setFrom( $this->from );
$e->setTemplate( $this->nlType->Template );
$e->populateTemplate( array( 'Member' => $member, 'FirstName' => $member->FirstName ) );
$this->sendToAddress( $e, $address, $this->messageID );
}
}
if( $this->current >= count( $this->objects ) )
return $this->complete();
else
return parent::next();
}
private function sendToAddress( $email, $address, $messageID = null ) {
$email->setTo( $address );
$email->send( $messageID );
}
function complete() {
parent::complete();
if( $this->newsletter->SentDate )
$resent = true;
$this->newsletter->SentDate = 'now';
$this->newsletter->Status = 'Send';
$this->newsletter->write();
$message = "statusMessage('Sent " . count( $this->objects ) . " emails successfully','good');";
if( $resent )
return $message."resent_ok( '{$this->nlType->ID}', '{$this->newsletter->ID}' )";
else
return $message."draft_sent_ok( '{$this->nlType->ID}', '{$this->newsletter->ID}' )";
}
}
?>

View File

@ -0,0 +1,57 @@
<?php
class NewsletterList extends FormField {
function __construct($name, $mailtype, $status = "Draft") {
if(is_object($mailtype)) $this->mailType = $mailtype;
else $this->mailType = DataObject::get_by_id('NewsletterType',$mailtype);
$this->status = $status;
parent::__construct(null);
}
function FieldHolder() {
return $this->renderWith("NewsletterList");
}
function setMailType($mailtype) {
$this->mailType = $mailtype;
}
function setController($controller) {
$this->controller = $controller;
}
function Newsletters() {
return DataObject::get( 'Newsletter', "`ParentID`='{$this->mailType->ID}' AND `Status`='{$this->status}'" );
}
function DraftNewsletters() {
return $this->mailType->DraftNewsletters();
}
function SentNewsletters() {
return $this->mailType->SentNewsletters();
}
function Status() {
return $this->status;
}
/* function AddRecordForm() {
return new TabularStyle(new Form($this->controller,'AddRecordForm',
new FieldSet(
new TextField("FirstName", "First Name"),
new TextField("Surname", "Surname"),
new TextField("Email", "Email"),
new TextField("Password", "Password"),
new HiddenField("GroupID", null, $this->group->ID)
),
new FieldSet(
new FormAction("addmember", "Add")
)
));
}*/
}
?>

View File

@ -0,0 +1,55 @@
<?php
class NewsletterType extends DataObject {
static $db = array(
"Title" => "Varchar",
"Template" => "Varchar",
"FromEmail" => "Varchar",
"Sent" => "Datetime"
);
static $has_one = array(
"Parent" => "SiteTree",
"Group" => "Group",
);
static $has_many = array(
"Newsletters" => "Newsletter",
);
function DraftNewsletters() {
return DataObject::get("Newsletter","ParentID={$this->ID} AND Status ='Draft'");
}
function SentNewsletters() {
return DataObject::get("Newsletter","ParentID={$this->ID} AND Status ='Send'");
}
function Recipients() {
return DataObject::get("Member", "Group_Members.GroupID = {$this->GroupID}", "", "JOIN Group_Members on Group_Members.MemberID = Member.ID");
}
function delete() {
foreach( $this->Newsletters() as $newsletter )
$newsletter->delete();
parent::delete();
}
/**
* Updates the group so the security section is also in sync with
* the curent newsletters.
*/
function onBeforeWrite(){
if($this->ID){
$group = $this->Group();
if($group->Title != "$this->Title"){
$group->Title = "Mailing List: " . $this->Title;
// Otherwise the code would have mailing list in it too :-(
$group->Code = SiteTree::generateURLSegment($this->Title);
$group->write();
}
}
parent::onBeforeWrite();
}
}
?>

View File

@ -0,0 +1,372 @@
<?php
/**
* Displays a file upload field.
*/
class RecipientImportField extends FormField {
protected $memberGroup;
protected $memberClass;
protected $tableColumns;
protected $table;
protected $clientFileName;
protected $typeID;
static $column_types = array(
'Salutation' => array( 'title', 'salutation' ),
'FirstName' => array( 'firstname', 'christianname', 'givenname' ),
'Surname' => array( 'lastname','surname', 'familyname' ),
'Email' => array( 'email', 'emailaddress' ),
'Address' => array( 'address' ),
'PhoneNumber' => array('phone','phonenumber'),
'JobTitle' => array( 'jobtitle' ),
'Organisation' => array( 'organisation', 'organization' ),
'EmailType' => array( 'htmlorplaintext', 'emailtype' )
);
static $custom_set_fields = array();
public static function setCustomField( $getMapCode, $fieldName ) {
self::$custom_set_fields[] = array(
'Code' => $getMapCode,
'Field' => $fieldName
);
}
function __construct( $name, $title, $memberGroup, $memberClass = 'Member', $form = null ) {
$this->memberGroup = $memberGroup;
$this->memberClass = $memberClass;
parent::__construct( $name, $title, null, $form );
}
function Field() {
$frameURL = Director::absoluteBaseURL() . 'admin/newsletter/displayfilefield/' . $this->typeID;
return "<iframe name=\"{$this->Name}\" frameborder=\"0\" class=\"RecipientImportField\" src=\"$frameURL\"></iframe>";
}
function setTypeID( $id ) {
$this->typeID = $id;
}
function CustomSetFields() {
$fields = new FieldSet();
foreach( self::$custom_set_fields as $customField ) {
eval( '$map = ' . $customField['Code'] .';' );
$noneMap = array( 0 => '(Not set)' );
$map = $noneMap + $map;
$fields->push( new DropdownField( 'Set['.$customField['Field'].']', $customField['Field'], $map ) );
}
return $fields;
}
/**
* Returns HTML to be displayed inside the IFrame
*/
static function fileupload() {
}
/**
* Returns the table of results to be displayed in the table of
* details loaded from the file
*/
function displaytable() {
// Check that a file was uploaded
$tempFile = fopen( $_FILES['ImportFile']['tmp_name'], 'r' );
// display some error if the file cannot be opened
$this->clientFileName = $_FILES['ImportFile']['name'];
while( ( $row = fgetcsv( $tempFile ) ) !== false ) {
if( !$this->tableColumns ) {
$this->parseTableHeader( $row );
} else {
$newRow = array();
$newSessionTableRow = array();
foreach( $row as $cell ) {
$newRow[] = $this->parseCSVCell( $cell );
$newSessionTableRow[] = $cell;
}
$cells = new DataObjectSet( $newRow );
$table[] = $cells->customise( array( 'Cells' => $cells ) );
$sessionTable[] = $newSessionTableRow;
}
}
fclose( $tempFile );
$this->table = new DataObjectSet( $table );
Session::set("ImportFile.{$_REQUEST['ID']}", $sessionTable);
return $this->renderWith( 'Newsletter_RecipientImportField_Table' );
}
/**
* Determines what type each column is
*/
function parseTableHeader( $columns ) {
$columnSource = array_combine(
array_keys( self::$column_types ),
array_keys( self::$column_types )
);
$columnSource = array_merge( array( 'Unknown' => 'Unknown' ), $columnSource );
foreach( $columns as $cell ) {
$columnType = $this->getColumnType( $this->parseCSVCell( $cell )->Value() );
$this->tableColumns[] = new DropdownField( 'ImportFileColumns[' . (string)( $colCount++ ) . ']', '', $columnSource, $columnType );
}
}
function parseCSVCell( $cell ) {
return new RecipientImportField_Cell( $cell );
}
function getColumnType( $cell ) {
$cell = strtolower( $cell );
$escapedValue = preg_replace( '/[^a-z]/', '', $cell );
foreach( self::$column_types as $type => $aliases ) {
if( in_array( $escapedValue, $aliases ) )
return $type;
}
return 'Unknown';
}
function setController( $controller ) {
$this->controller = $controller;
}
/**
* Set of table column headers
*/
function ColumnHeaders() {
return new DataObjectSet( $this->tableColumns );
}
function Rows() {
return $this->table;
}
function FileName() {
return $this->clientFileName;
}
function TypeID() {
return $this->typeID;
}
}
class RecipientImportField_Cell extends ViewableData {
protected $value;
function __construct( $value ) {
$this->value = $value;
}
function Value() {
return $this->value;
}
}
class RecipientImportField_UploadForm extends Form {
function import( $data, $form ) {
$id = $data['ID'];
$mailType = DataObject::get_one("NewsletterType", "ID = $id");
if($mailType->GroupID)
$group = DataObject::get_one("Group", "ID = $mailType->GroupID");
$recipientField = new RecipientImportField("ImportFile","Import from file", $group );
$recipientField->setTypeID( $id );
// if the file is not valid, then return an error
if( empty( $_FILES ) || empty( $_FILES['ImportFile'] ) || $_FILES['ImportFile']['size'] == 0 )
return $this->customise( array( 'ID' => $id, "UploadForm" => $this->controller->UploadForm( $id ), 'ErrorMessage' => 'Please choose a CSV file to import' ) )->renderWith('Newsletter_RecipientImportField');
elseif( !$this->isValidCSV( $_FILES['ImportFile'] ) ) {
/*if( file_exists( $_FILES['ImportFile']['tmp_name'] ) ) unlink( $_FILES['ImportFile']['tmp_name'] );
unset( $_FILES['ImportFile'] );*/
return $this->customise( array( 'ID' => $id, "UploadForm" => $this->controller->UploadForm( $id ), 'ErrorMessage' => 'The selected file was not a CSV file' ) )->renderWith('Newsletter_RecipientImportField');
} else
return $recipientField->displaytable();
}
function isValidCSV( $file ) {
return preg_match( '/.*\.csv$/', $file['name'] ) > 0;
}
function confirm( $data, $form ) {
$id = $data['ID'];
$mailType = DataObject::get_one("NewsletterType", "ID = $id");
if($mailType->GroupID)
$group = DataObject::get_one("Group", "ID = $mailType->GroupID");
return $this->importMembers( $id, $group, $data['ImportFileColumns'], $data['Set'] );
}
function cancel( $data, $form ) {
$newForm = $this->controller->UploadForm( $data['ID'] );
return $newForm->forTemplate();
}
function action_import( $data, $form ) {
return $this->import( $data, $form );
}
function action_confirm( $data, $form ) {
return $this->confirm( $data, $form );
}
function action_cancel( $data, $form ) {
return $this->cancel( $data, $form );
}
/**
* Import members from the uploaded file
*/
protected function importMembers( $id, $group, $cols, $setFields, $primaryColType = 'Email' ) {
$startTime = time();
$importData = Session::set("ImportFile.{$id}");
$validColumns = array_keys( RecipientImportField::$column_types );
$columnMap = array_flip( $cols );
// Debug::show($columnMap);
// locate the primary column's index
$primaryColumn = /*array_search( $primaryColType, $validColumns );*/ $columnMap[$primaryColType];
// changed fields
$changedFields = array();
// intersect the list of valid columns with the column map to find the columns we need
$importColumns = array_intersect( $validColumns, $cols );
// statistics
$newMembers = 0;
$changedMembers = 0;
$skippedMembers = 0;
// the class that the imported members will become
$newMemberClass = Object::getCustomClass( 'Member' );
// for each row, add a new member or overwrite an existing member
foreach( $importData as $newMemberRow ) {
// skip rows with an empty value for the primary column
if( empty( $newMemberRow[$primaryColumn] ) ) {
$skippedMembers++;
continue;
}
// remember to check if the user has unsubscribed
$trackChanges = true;
// TODO: Write DataObject::update
$member = $this->findMember( $newMemberRow[$primaryColumn] );
if( !$member ) {
$newMembers++;
$trackChanges = false;
$member = Object::create("Member");
} else {
// skip this member if the are unsubscribed
if( $member->Unsubscribed ) {
$skippedMembers++;
continue;
}
if( $member->class != $newMemberClass )
$member->setClassName( $newMemberClass );
$changedMembers++;
}
// add each of the valid columns
foreach( $importColumns as $datum ) {
// perform any required conversions
$newValue = trim( $newMemberRow[$columnMap[$datum]] );
$oldValue = trim( $member->$datum );
// Debug::message( "$datum@{$columnMap[$datum]}" );
// track the modifications to the member data
if( $trackChanges && $newValue != $oldValue && $datum != $primaryColumn ) {
$changedFields[] = array(
$newMemberRow[$primaryColumn],
"$datum:\n$oldValue",
"$datum:\n$newValue"
);
$numChangedFields++;
}
$member->$datum = $newValue;
}
// set any set fields
if( $setFields )
foreach( $setFields as $fieldName => $fieldValue )
$member->$fieldName = $fieldValue;
// add member to group
$member->write();
$member->Groups()->add( $group->ID );
}
$numChangedFields = count( $changedFields );
$this->notifyChanges( $changedFields );
// TODO Refresh window
$customData = array(
'ID' => $id,
"UploadForm" => $this->controller->UploadForm( $id ),
'ImportMessage' => 'Imported new members',
'NewMembers' => (string)$newMembers,
'ChangedMembers' => (string)$changedMembers,
'ChangedFields' => (string)$numChangedFields,
'SkippedRecords' => (string)$skippedMembers,
'Time' => time() - $startTime
);
return $this->customise( $customData )->renderWith('Newsletter_RecipientImportField');
}
function findMember( $email ) {
$email = addslashes( $email );
return DataObject::get_one( 'Member', "`Email`='$email'" );
}
function notifyChanges( $changes ) {
$email = new Email( Email::getAdminEmail(), Email::getAdminEmail(), 'Changed Fields' );
$body = "";
foreach( $changes as $change ) {
$body .= "-------------------------------\n";
$body .= implode( ' ', $change ) . "\n";
}
$email->setBody( $body );
$email->send();
}
}
?>

254
code/Newsletter/SubscribeForm.php Executable file
View File

@ -0,0 +1,254 @@
<?php
class SubscribeForm extends UserDefinedForm {
static $add_action = "a newsletter subscription form";
static $required_fields = array(
'Email address' => 'EditableEmailField(CustomParameter=Email,CanDelete=0)',
'First name' => 'EditableTextField(CustomParameter=FirstName)',
'Last name' => 'EditableTextField(CustomParameter=Surname)',
'Address' => 'EditableTextField(Rows=3,CustomParameter=Address)',
'Job title' => 'EditableTextField(CustomParameter=JobTitle)',
'Organisation' => 'EditableTextField(CustomParameter=Organisation)',
'Mail format' => 'EditableRadioField(Options=1:HTML,Options=0:Text__only,CustomParameter=HTMLEmail)'
);
static $db = array(
'Subscribe' => 'Boolean',
'AllNewsletters' => 'Boolean',
'Subject' => 'Varchar'
);
static $defaults = array(
"OnCompleteMessage" => "<p>Thanks, you have been added to our mailing list.</p>",
);
static $has_many = array(
'Newsletters' => 'NewsletterType'
);
function __construct( $data = null, $isSingleton = false ) {
parent::__construct( $data, $isSingleton );
if( $data || $isSingleton )
return;
$this->addDefaultFields();
}
private function addDefaultFields() {
$f = $this->Fields();
// check that the required fields exist
$count = 1;
foreach( self::$required_fields as $defaultName => $typeString ) {
list( $type, $typeValue ) = $this->parseType( $typeString );
$newField = new $type();
$newField->Name = "Field" . (string)$count;
$newField->Title = $defaultName;
// TODO Map the field to a particular action
if( !empty( $typeValue ) ) {
$newField->prepopulate( $typeValue );
}
$newField->setField('ID', "new-" . $count);
$newField->Sort = $count;
$f->addWithoutWrite($newField);
$count++;
}
}
public function Newsletters() {
$components = $this->getComponents('Newsletters');
return $components;
}
public function customFormActions( $isReadonly = false ) {
$fields = parent::customFormActions( $isReadonly );
// get the newsletters in the system
$newsletterTypes = DataObject::get( 'NewsletterType' );
$availableNewsletters = array();
foreach( $this->Newsletters() as $subscribeTo )
$availableNewsletters[] = $subscribeTo->ID;
// create a checkbox for each newsletter
if($newsletterTypes && is_object($newsletterTypes)) {
foreach( $newsletterTypes as $newsletterType )
$nlCheckboxes[] = new CheckboxField( "CustomNewsletters[{$newsletterType->ID}]", $newsletterType->Title, in_array( $newsletterType->ID, $availableNewsletters ) );
}
$fields->push( new OptionsetField( 'AllNewsletters', '', array(
1 => 'All newsletters',
0 => 'Specific newsletters'
),
$this->AllNewsletters
));
$fields->push( new CompositeField( $nlCheckboxes ));
$fields->push( new TextField( 'Subject', 'Subject line on confirmation', $this->Subject ) );
return $fields;
}
function parseType( $typeString ) {
if( preg_match('/^([A-Za-z_]+)\(([^)]+)\)$/', $typeString, $match ) )
return array( $match[1], $match[2] );
return array( $typeString, null );
}
/**
*
*/
public function processNewFormFields() {
}
/**
* Saves data related to any custom actions this form performs when submitted
*/
public function customFormSave( $data, $form ) {
$newsletters = $this->Newsletters();
$this->Subscribe = !empty( $data['Subscribe'] );
$this->AllNewsletters = !empty( $data['AllNewsletters'] );
$this->Subject = $data['Subject'];
// Note: $data['CustomNewsletters'] was changed to $_REQUEST['CustomNewsletters'] in order to fix
// to fix "'Specific newsletters' option in 'newsletter subscription form' page type does not work" bug
// See: http://www.silverstripe.com/bugs/flat/1675
if( !empty( $_REQUEST['CustomNewsletters'] ) ) {
/*foreach( $newsletters as $newsletter ) {
if( $data['CustomNewsletters'][$newsletter->ID] == 'on' ) {
$newsletters->add( $newsletter->ID );
} else {
$newsletters->remove( $newsletter->ID );
}
}*/
if( isset($_REQUEST['CustomNewsletters'][0]) )
unset( $_REQUEST['CustomNewsletters'][0] );
$newsletters->setByIDList( array_keys( $_REQUEST['CustomNewsletters'] ) );
} else {
$this->AllNewsletters = true;
$newsletters->removeAll();
}
}
}
class SubscribeForm_SubscribeEmail extends Email_Template {
protected $to = '$Email';
protected $subject = '$Subject';
protected $ss_template = 'SubscribeEmail';
protected $from = '';
}
class SubscribeForm_Controller extends UserDefinedForm_Controller {
function process( $data, $form ) {
// Add the user to the mailing list
$member = Object::create("Member");
// $_REQUEST['showqueries'] = 1;
// map the editables to the data
foreach( $this->Fields() as $editable ) {
$field = $editable->CustomParameter;
if( !$field )
continue;
// Debug::message( $editable->Name . '->' . $field );
// if( $member->hasField( $field ) )
$member->$field = $data[$editable->Name];
}
// need to write the member record before adding the user to groups
$member->write();
$newsletters = array();
// Add member to the selected newsletters
if( $data['Newsletters'] ) foreach( $data['Newsletters'] as $listID ) {
if( !is_numeric( $listID ) )
continue;
// get the newsletter
$newsletter = DataObject::get_by_id( 'NewsletterType', $listID );
if( !$newsletter )
continue;
$newsletters[] = $newsletter;
// Debug::show( $newsletter->GroupID );
$member->Groups()->add( $newsletter->GroupID );
}
// email the user with their details
$templateData = array(
'Email' => $member->Email,
'FirstName' => $member->FirstName,
'Newsletters' => new DataObjectSet( $newsletters ),
'UnsubscribeLink' => Director::baseURL() . 'unsubscribe/' . $member->Email
);
$email = new SubscribeForm_SubscribeEmail();
$email->setFrom( Email::getAdminEmail() );
$email->setSubject( $this->Subject );
$email->populateTemplate( $templateData );
$email->send();
$parentHTML = parent::process( $data, $form );
$templateData['Link'] = $data['Referrer'];
$templateData['UnsubscribeLink'] = Director::baseURL() . 'unsubscribe';
$custom = $this->customise(array(
"Content" => $this->customise( $templateData )->renderWith('SubscribeSubmission')
));
return $custom->renderWith('Page');
}
function Form() {
$form = parent::Form();
if( $this->AllNewsletters )
$newsletterList = DataObject::get('NewsletterType');
else
$newsletterList = $this->Newsletters();
$newsletters = array();
// Debug::show($newsletterList);
// get the newsletter types to display on the form
foreach( $newsletterList as $newsletter )
$newsletters[] = $newsletter;
// Debug::show( $newsletters );
$form->Fields()->push( new CheckboxSetField( 'Newsletters', 'Subscribe to lists', $newsletters ) );
$validator = $form->getValidator();
$validator->addRequiredField( 'Newsletters' );
$form->setValidator( $validator );
return $form;
}
}
?>

View File

@ -0,0 +1,40 @@
<?php
/**
* This should extend DropdownField
*/
class TemplateList extends DropdownField {
protected $templatePath;
function __construct( $name, $title, $value, $path, $form = null ) {
$this->templatePath = $path;
parent::__construct( $name, $title, $this->getTemplates(), $value, $form );
}
private function getTemplates() {
$templates = array( "" => "None" );
$absPath = Director::baseFolder();
if( $absPath{strlen($absPath)-1} != "/" )
$absPath .= "/";
$absPath .= $this->templatePath;
if(is_dir($absPath)) {
$templateDir = opendir( $absPath );
// read all files in the directory
while( ( $templateFile = readdir( $templateDir ) ) !== false ) {
// *.ss files are templates
if( preg_match( '/(.*)\.ss$/', $templateFile, $match ) )
$templates[$match[1]] = $match[1];
}
}
return $templates;
}
function setController( $controller ) {
$this->controller = $controller;
}
}
?>

178
code/Newsletter/Unsubscribe.php Executable file
View File

@ -0,0 +1,178 @@
<?php
/**
* Create a form that a user can use to unsubscribe from a mailing list
*/
class Unsubscribe_Controller extends Page_Controller {
function __construct($data = null) {
}
function RelativeLink($action = null) {
return "unsubscribe/$action";
}
function index() {
Session::clear("loggedInAs");
Requirements::css(project()."/css/form.css");
// if the email address is given
$emailAddress = addslashes( $this->urlParams['Email'] );
$mailingListID = addslashes( $this->urlParams['MailingList'] );
if(is_numeric($mailingListID)) {
$mailingList = DataObject::get_by_id("NewsletterType", $mailingListID);
}
// try to find the user
if($emailAddress)
$member = DataObject::get_one( 'Member', "`Email`='$emailAddress'" );
// if the email address and mailing list is given in the URL and both are valid,
// then unsubscribe the user
if( $member && $mailingList && $member->inGroup( $mailingList->GroupID ) ) {
$this->unsubscribeFromList( $member, $mailingList );
$url = "unsubscribe"."/done/".$member->Email."/".$mailingList->Title;
Director::redirect($url);
} elseif( $member ) {
$listForm = $this->MailingListForm( $member );
} else {
$listForm = $this->EmailAddressForm();
}
if($this->urlParams['Email'] == "done")
$listForm->sessionMessage("Thank you. You have been removed from the selected groups", "good");
return $this->customise( array( 'Content' => $listForm->forTemplate() ) )->renderWith('Page');
}
/**
* Display a form with all the mailing lists that the user is subscribed to
*/
function MailingListForm( $member = null ) {
$email = $this->urlParams['Email'];
return new Unsubscribe_MailingListForm($this, 'MailingListForm', $member, $email);
}
/**
* Display a form allowing the user to input their email address
*/
function EmailAddressForm() {
return new Unsubscribe_EmailAddressForm( $this, 'EmailAddressForm' );
}
/**
* Show the lists for the user with the given email address
*/
function showlists( $data, $form ) {
$member = DataObject::get_one( 'Member', "`Email`='{$data['Email']}'" );
$mailingListForm = new Unsubscribe_MailingListForm( $this, 'MailingListForm', $member, $data['Email']);
return $this->customise( array( 'Content' => $mailingListForm->forTemplate() ) )->renderWith('Page');
}
/**
* Unsubscribe the user from the given lists.
*/
function unsubscribe($data, $form) {
$email = $this->urlParams['Email'];
$member = DataObject::get_one( 'Member', "`Email`='$email'" );
if(!$member){
$member = DataObject::get_one('Member', "`EmailAddress` = '$email'");
}
if( $data['MailingLists'] ) {
foreach( array_keys( $data['MailingLists'] ) as $listID ){
$nlType = DataObject::get_by_id( 'NewsletterType', $listID );
$nlTypeTitles[]= $nlType->Title;
$this->unsubscribeFromList( $member, DataObject::get_by_id( 'NewsletterType', $listID ) );
}
$sORp = (sizeof($nlTypeTitles)>1)?"newsletters ":"newsletter ";
//means single or plural
$nlTypeTitles = $sORp.implode(", ", $nlTypeTitles);
$url = "unsubscribe/done/".$member->Email."/".$nlTypeTitles;
Director::redirect($url);
} else {
$form->addErrorMessage('MailingLists', 'You need to select at least one mailing list to unsubscribe from.', 'bad');
Director::redirectBack();
}
}
protected function unsubscribeFromList( $member, $list ) {
// track unsubscriptions
$member->Groups()->remove( $list->GroupID );
}
}
class Unsubscribe_MailingListForm extends Form {
protected $memberEmail;
function __construct( $controller, $name, $member, $email ) {
$this->memberEmail = $member->Email;
$fields = new FieldSet();
$actions = new FieldSet();
// get all the mailing lists for this user
$lists = $this->getMailingLists( $member );
if( $lists ) {
$fields->push( new LabelField( 'You are subscribed to the following lists:' ) );
foreach( $lists as $list ) {
$fields->push( new CheckboxField( "MailingLists[{$list->ID}]", $list->Title ) );
}
$actions->push( new FormAction('unsubscribe', 'Unsubscribe' ) );
} else {
$fields->push( new LabelField( "I'm sorry, but $email doesn't appear to be in any of our mailing lists." ) );
}
parent::__construct( $controller, $name, $fields, $actions );
}
function FormAction() {
return $this->controller->Link() . "{$this->memberEmail}?executeForm=" . $this->name;
}
protected function getMailingLists( $member ) {
// get all the newsletter types that the member is subscribed to
return DataObject::get( 'NewsletterType', "`MemberID`='{$member->ID}'", null, "LEFT JOIN `Group_Members` USING(`GroupID`)" );
}
}
class Unsubscribe_EmailAddressForm extends Form {
function __construct( $controller, $name ) {
$fields = new FieldSet(
new EmailField( 'Email', 'Email address' )
);
$actions = new FieldSet(
new FormAction( 'showlists', 'Show lists' )
);
parent::__construct( $controller, $name, $fields, $actions );
}
function FormAction() {
return parent::FormAction() . ( $_REQUEST['showqueries'] ? '&showqueries=1' : '' );
}
}
class Unsubscribe_Successful extends Form {
function __construct($controller, $name){
$fields = new FieldSet();
$actions = new FieldSet();
parent::__construct($controller, $name, $fields, $actions);
}
function setSuccessfulMessage($email, $newsletterTypes) {
Requirements::css(project() . "/css/form.css");
$this->setMessage("Thank you. $email will no longer receive the $newsletterTypes.", "good");
}
}
?>

View File

@ -0,0 +1,49 @@
<?php
/**
* Displays a list of all members that have unsubscribed from the list
*/
class UnsubscribedList extends FormField {
protected $nlType;
function __construct( $name, $newsletterType ) {
parent::__construct( $name, '', null );
if( is_object( $newsletterType ) )
$this->nlType = $newsletterType;
else
$this->nlType = DataObject::get_by_id( 'NewsletterType', $newsletterType );
}
function FieldHolder() {
return $this->renderWith( 'NewsletterAdmin_UnsubscribedList' );
}
function Entries() {
$id = $this->nlType->ID;
$unsubscribeRecords = DataObject::get( 'Member_UnsubscribeRecord', "`NewsletterTypeID`='$id'" );
// user_error($id, E_USER_ERROR );
if( !$unsubscribeRecords )
return null;
foreach( $unsubscribeRecords as $unsubscribeRecord ) {
if( $unsubscribeRecord ) {
$unsubscribedUsers[] = new ArrayData( array(
'Record' => $unsubscribeRecord,
'Member' => DataObject::get_by_id( 'Member', $unsubscribeRecord->MemberID )
));
}
}
return new DataObjectSet( $unsubscribedUsers );
}
function setController($controller) {
$this->controller = $controller;
}
}
?>

686
code/NewsletterAdmin.php Executable file
View File

@ -0,0 +1,686 @@
<?php
class NewsletterAdmin extends LeftAndMain {
static $subitem_class = "Member";
static $template_path = null; // defaults to (project)/templates/email
public function init() {
// Check permissions
// if(!Member::currentUser() || !Member::currentUser()->isAdmin()) Security::permissionFailure($this);
parent::init();
/*
if(!$this->can('AdminCMS')) {
$messageSet = array(
'default' => "Enter your email address and password to access the CMS.",
'alreadyLoggedIn' => "I'm sorry, but you can't access that part of the CMS. If you want to log in as someone else, do so below",
'logInAgain' => "You have been logged out of the CMS. If you would like to log in again, enter a username and password below.",
);
Security::permissionFailure($this, $messageSet);
return;
}*/
Requirements::javascript(MCE_ROOT . "tiny_mce_src.js");
Requirements::javascript("jsparty/tiny_mce_improvements.js");
Requirements::javascript("jsparty/hover.js");
Requirements::javascript("jsparty/scriptaculous/controls.js");
Requirements::javascript("cms/javascript/SecurityAdmin.js");
Requirements::javascript("cms/javascript/LeftAndMain_left.js");
Requirements::javascript("cms/javascript/LeftAndMain_right.js");
Requirements::javascript("cms/javascript/CMSMain_left.js");
Requirements::javascript("cms/javascript/NewsletterAdmin_left.js");
Requirements::javascript("cms/javascript/NewsletterAdmin_right.js");
Requirements::javascript("sapphire/javascript/ProgressBar.js");
// We don't want this showing up in every ajax-response, it should always be present in a CMS-environment
if(!Director::is_ajax()) {
Requirements::javascriptTemplate("cms/javascript/tinymce.template.js", array(
"ContentCSS" => project() . "/css/editor.css",
"BaseURL" => Director::absoluteBaseURL(),
));
}
// needed for MemberTableField (Requirements not determined before Ajax-Call)
Requirements::javascript("jsparty/greybox/AmiJS.js");
Requirements::javascript("jsparty/greybox/greybox.js");
Requirements::javascript("sapphire/javascript/TableListField.js");
Requirements::javascript("sapphire/javascript/TableField.js");
Requirements::javascript("sapphire/javascript/ComplexTableField.js");
Requirements::javascript("cms/javascript/MemberTableField.js");
Requirements::css("jsparty/greybox/greybox.css");
Requirements::css("sapphire/css/ComplexTableField.css");
}
public function remove() {
$ids = explode( ',', $_REQUEST['csvIDs'] );
foreach( $ids as $id ) {
if( preg_match( '/^mailtype_(\d+)$/', $id, $matches ) )
$record = DataObject::get_by_id( 'NewsletterType', $matches[1] );
else if( preg_match( '/^[a-z]+_\d+_(\d+)$/', $id, $matches ) )
$record = DataObject::get_by_id( 'Newsletter', $matches[1] );
if($record) {
$record->delete();
}
FormResponse::add("removeTreeNodeByIdx(\$('sitetree'), '$id' );");
}
FormResponse::status_message('Deleted $count items','good');
return FormResponse::respond();
}
public function getformcontent(){
Session::set('currentPage', $_REQUEST['ID']);
Session::set('currentType', $_REQUEST['type']);
if($_REQUEST['otherID'])
Session::set('currentOtherID', $_REQUEST['otherID']);
SSViewer::setOption('rewriteHashlinks', false);
$result = $this->renderWith($this->class . "_right");
return $this->getLastFormIn($result);
}
public function shownewsletter($params) {
return $this->showWithEditForm( $params, $this->getNewsletterEditForm( $params['ID'] ) );
}
public function showtype($params) {
return $this->showWithEditForm( $params, $this->getNewsletterTypeEditForm( $params['ID'] ) );
}
public function removenewsletter($params) {
if( !is_numeric( $params['ID'] ) )
return '';
$newsletter = DataObject::get_by_id( 'Newsletter', $params['ID'] );
$newsletter->delete();
return 'letter-' . $params['ID'];
}
private function showWithEditForm( $params, $editForm ) {
if(isset($params['ID'])) {
Session::set('currentPage', $params['ID']);
}
if(isset($params['OtherID'])) {
Session::set('currentMember', $params['OtherID']);
}
if(Director::is_ajax()) {
SSViewer::setOption('rewriteHashlinks', false);
return $editForm->formHtmlContent();
} else {
return array();
}
}
public function getEditForm( $id ) {
return $this->getNewsletterTypeEditForm( $id );
}
/**
* Get the EditForm
*/
public function EditForm() {
if((isset($_REQUEST['ID']) && $_REQUEST['Type'] == 'Newsletter') || isset($_REQUEST['action_savenewsletter'])) {
return $this->NewsletterEditForm();
} else {
return $this->TypeEditForm();
}
}
public function NewsletterEditForm() {
$id = $_REQUEST['ID'] ? $_REQUEST['ID'] : $this->currentPageID();
if(!is_numeric($id)) {
$id = 0;
}
return $this->getNewsletterEditForm($id);
}
public function TypeEditForm() {
$id = isset($_REQUEST['ID']) ? $_REQUEST['ID'] : $this->currentPageID();
if(!is_numeric($id)) {
$id = 0;
}
return $this->getNewsletterTypeEditForm($id);
}
public function getNewsletterTypeEditForm($id) {
if(!is_numeric($id)) {
$id = Session::get('currentPage');
}
if( is_a( $id, 'NewsletterType' ) ) {
$mailType = $id;
$id = $mailType->ID;
} else {
if($id && is_numeric($id)) {
$mailType = DataObject::get_by_id( 'NewsletterType', $id );
}
}
if(isset($mailType) && is_object($mailType) && $mailType->GroupID) {
$group = DataObject::get_one("Group", "ID = $mailType->GroupID");
}
if(isset($mailType)) {
$fields = new FieldSet(
new TextField("Title", "Newsletter Type"),
new TextField("FromEmail", "Send newsletters from"),
new TabSet("Root",
new Tab("Drafts",
$draftList = new NewsletterList("Draft", $mailType, "Draft")
),
new TabSet("Sent",
new Tab("Sent",
$sendList = new NewsletterList("Send", $mailType, "Send")
),
new Tab("Unsubscribed",
$unsubscribedList = new UnsubscribedList("Unsubscribed", $mailType)
),
new Tab("Bounced",
$bouncedList = new BouncedList("Bounced", $mailType )
)
),
new TabSet("Recipients",
new Tab( "Recipients",
$recipients = new MemberTableField(
$this,
"Recipients",
$group
)
),
new Tab( "Import",
$importField = new RecipientImportField("ImportFile","Import from file", $group )
)
),
new Tab("Template",
$templates = new TemplateList("Template","Template", $mailType->Template, self::template_path())
)
)
);
$draftList->setController($this);
$sendList->setController($this);
$recipients->setController($this);
$templates->setController($this);
$importField->setController($this);
$unsubscribedList->setController($this);
$bouncedList->setController($this);
$importField->setTypeID( $id );
$fields->push($idField = new HiddenField("ID"));
$fields->push( new HiddenField( "executeForm", "", "TypeEditForm" ) );
$idField->setValue($id);
// $actions = new FieldSet(new FormAction('adddraft', 'Add Draft'));
$actions = new FieldSet(new FormAction('save','Save'));
$form = new Form($this, "EditForm", $fields, $actions);
$form->loadDataFrom(array(
'Title' => $mailType->Title,
'FromEmail' => $mailType->FromEmail
));
} else {
$form = false;
}
return $form;
}
/*
public function showmailinglist($params) {
return $this->showWithEditForm( $params, $this->getMailinglistEditForm( $params['ID'] ) );
}
public function getMailinglistEditForm($id) {
if(!is_numeric($id)) {
$id = $_SESSION['currentPage'];
}
if( is_a( $id, 'NewsletterType' ) ) {
$mailType = $id;
$id = $mailType->ID;
} else {
if($id && is_numeric($id)) {
$mailType = DataObject::get_by_id( 'NewsletterType', $id );
}
}
if($mailType->GroupID) {
$group = DataObject::get_one("Group", "ID = $mailType->GroupID");
}
if($mailType) {
$fields = new FieldSet(
new TabSet("Recipients",
new Tab( "Recipients",
$recipients = new MemberTableField(
$this,
"Recipients",
$group
)
),
new Tab( "Import",
$importField = new RecipientImportField("ImportFile","Import from file", $group )
)
)
);
$recipients->setController($this);
$importField->setController($this);
$importField->setTypeID( $id );
$fields->push($idField = new HiddenField("ID"));
$fields->push( new HiddenField( "executeForm", "", "TypeEditForm" ) );
$idField->setValue($id);
$form = new Form($this, "EditForm", $fields, new FieldSet());
}
}
*/
/**
* Reloads the list of recipients via ajax
*/
function getrecipientslist() {
if( $_REQUEST['ajax'] ) {
$newsletterType = DataObject::get_by_id('NewsletterType', $this->urlParams['ID'] );
$memberList = new MemberTableField($this, "Recipients", $newsletterType->Group() );
return $memberList->FieldHolder();
}
}
public static function template_path() {
if(self::$template_path) return self::$template_path;
else return self::$template_path = project() . '/templates/email';
}
public function showdraft( $params ) {
return $this->showWithEditForm( $params, $this->getNewsletterEditForm( $params['ID'] ) );
}
public function getNewsletterEditForm($myId){
$email = DataObject::get_by_id("Newsletter", $myId);
if($email) {
$fields = $email->getCMSFields($this);
$fields->push($idField = new HiddenField("ID"));
$idField->setValue($myId);
$fields->push($typeField = new HiddenField("Type"));
$typeField->setValue('Newsletter');
//$fields->push(new HiddenField("executeForm", "", "EditForm") );
$actions = new FieldSet();
if( $email->SentDate )
$actions->push(new FormAction('send','Resend'));
else
$actions->push(new FormAction('send','Send'));
$actions->push(new FormAction('save','Save'));
$form = new Form($this, "EditForm", $fields, $actions);
$form->loadDataFrom($email);
if($email->Status != 'Draft') {
$form->makeReadonly();
}
// user_error( $form->FormAction(), E_USER_ERROR );
return $form;
} else {
user_error( 'Unknown Email ID: ' . $myId, E_USER_ERROR );
}
}
public function SendProgressBar() {
$progressBar = new ProgressBar( 'SendProgressBar', 'Sending emails...' );
return $progressBar->FieldHolder();
}
public function sendnewsletter( /*$data, $form = null*/ ) {
$id = $_REQUEST['ID'] ? $_REQUEST['ID'] : $_REQUEST['NewsletterID'];
if( !$id ) {
FormResponse::status_message('No newsletter specified','bad');
return FormResponse::respond();
}
$newsletter = DataObject::get_by_id("Newsletter", $id);
$nlType = $newsletter->getNewsletterType();
$e = new Newsletter_Email($nlType);
$e->Body = $body = $newsletter->Content;
$e->Subject = $subject = $newsletter->Subject;
// TODO Make this dynamic
if( $nlType && $nlType->FromEmail )
$e->From = $from = $nlType->FromEmail;
else
$e->From = $from = Email::getAdminEmail();
$e->To = $_REQUEST['TestEmail'];
$e->setTemplate( $nlType->Template );
$messageID = base64_encode( $newsletter->ID . '_' . date( 'd-m-Y H:i:s' ) );
switch($_REQUEST['SendType']) {
case "Test":
if($_REQUEST['TestEmail']) {
if( $nlType->Template ) {
self::sendToAddress( $e, $_REQUEST['TestEmail'], $messageID );
FormResponse::status_message('Sent test to ' . $_REQUEST['TestEmail'],'good');
} else {
FormResponse::status_message('No template selected','bad');
}
} else {
FormResponse::status_message('Please enter an email address','bad');
}
break;
case "List":
echo self::sendToList( $subject, $body, $from, $newsletter, $nlType, $messageID );
}
return FormResponse::respond();
}
static function sendToAddress( $email, $address, $messageID = null ) {
$email->To = $address;
$email->send();
}
static function sendToList( $subject, $body, $from, $newsletter, $nlType, $messageID = null ) {
$emailProcess = new NewsletterEmailProcess( $subject, $body, $from, $newsletter, $nlType, $messageID );
return $emailProcess->start();
/*$groupID = $nlType->GroupID;
$members = DataObject::get( 'Member', "`GroupID`='$groupID'", null, "INNER JOIN `Group_Members` ON `MemberID`=`Member`.`ID`" );
// user_error( $members, E_USER_ERROR );
if( !$members )
return "statusMessage('Unable to retrieve members from mailing list', 'bad' )";
foreach( $members as $member ) {
// check to see if the user has unsubscribed from the mailing list
$unsubscribeRecord = DataObject::get_one('Member_UnsubscribeRecord', "`MemberID`= {$member->ID} AND `NewsletterTypeID`={$nlType->ID}");
if( !$unsubscribeRecord ) {
$e = new Newsletter_Email($nlType);
$e->Body = $body;
$e->Subject =$subject;
$e->From = $from;
$e->setTemplate( $nlType->Template );
$e->populateTemplate( array( 'Member' => $member, 'FirstName' => $member->FirstName ) );
$this->sendToAddress( $e, $member->Email, $messageID );
}
}
if( $newsletter->Sent )
$resent = true;
$newsletter->Sent = 'now';
$newsletter->Status = 'Send';
$newsletter->write();
if( $resent )
return "resent_ok( '{$nlType->ID}', '{$newsletter->ID}' )";
else
return "draft_sent_ok( '{$nlType->ID}', '{$newsletter->ID}' )";*/
}
public function save($urlParams, $form) {
if( $_REQUEST['Type'] && $_REQUEST['Type'] == 'Newsletter' )
return $this->savenewsletter( $urlParams, $form );
$id = $_REQUEST['ID'];
$record = DataObject::get_one('NewsletterType', "`$className`.ID = $id");
// Is the template attached to the type, or the newsletter itself?
$record->Template = addslashes( $_REQUEST['Template'] );
$form->saveInto($record);
$record->write();
FormResponse::set_node_title("mailtype_$id", $record->Title);
FormResponse::status_message('Saved', 'good');
return FormResponse::respond();
}
public function savenewsletter($urlParams, $form) {
$id = $_REQUEST['ID'];
$record = DataObject::get_one('Newsletter', "`$className`.ID = $id");
// Is the template attached to the type, or the newsletter itself?
$type = $record->getNewsletterType();
$record->Subject = $urlParams['Subject'];
$record->Content = $urlParams['Content'];
$record->write();
$id = 'draft_'.$record->ParentID.'_'.$record->ID;
FormResponse::set_node_title($id, $record->Title);
FormResponse::status_message('Saved', 'good');
return FormResponse::respond();
}
function NewsletterAdminSiteTree() {
return $this->getsitetree();
}
function getsitetree() {
return $this->renderWith('NewsletterAdmin_SiteTree');
}
public function AddRecordForm() {
$m = new MemberTableField($this,"Members", $this->currentPageID());
return $m->AddRecordForm();
}
/**
* Ajax autocompletion
*/
public function autocomplete() {
$fieldName = $this->urlParams['ID'];
$fieldVal = $_REQUEST[$fieldName];
$matches = DataObject::get("Member","$fieldName LIKE '" . addslashes($fieldVal) . "%'");
if($matches) {
echo "<ul>";
foreach($matches as $match) {
$data = $match->FirstName;
$data .= ",$match->Surname";
$data .= ",$match->Email";
$data .= ",$match->Password";
echo "<li>" . $match->$fieldName . "<span class=\"informal\">($match->FirstName $match->Surname, $match->Email)</span><span class=\"informal data\">$data</li>";
}
echo "</ul>";
}
}
function savemember() {
$data = $_REQUEST;
$className = $this->stat('subitem_class');
$id = $_REQUEST['ID'];
if($id == 'new') $id = null;
if($id) {
$record = DataObject::get_one($className, "`$className`.ID = $id");
} else {
// send out an email to notify the user that they have been subscribed
$record = new $className();
}
$record->update($data);
$record->ID = $id;
$record->write();
$record->Groups()->add($data['GroupID']);
$FirstName = Convert::raw2js($record->FirstName);
$Surname = Convert::raw2js($record->Surname);
$Email = Convert::raw2js($record->Email);
$Password = Convert::raw2js($record->Password);
$response = <<<JS
$('MemberList').setRecordDetails($record->ID, {
FirstName : "$FirstName",
Surname : "$Surname",
Email : "$Email"
});
$('MemberList').clearAddForm();
JS;
FormResponse::add($response);
FormResponse::status_message('Saved', 'good');
return FormResponse::respond();
}
public function NewsletterTypes() {
return DataObject::get("NewsletterType","");
}
public function addgroup() {
$parent = $_REQUEST['ParentID'] ? $_REQUEST['ParentID'] : 0;
$p = new Group();
$p->Title = "New Group";
$p->Code = "new-group";
$p->ParentID = $parent;
$p->write();
$this->returnItemToUser($p);
}
public function addtype( $params ) {
switch( $_REQUEST['PageType'] ) {
case 'type':
$form = $this->getNewsletterTypeEditForm( $this->newNewsletterType() );
break;
default:
$form = $this->getNewsletterEditForm( $this->newDraft( $_REQUEST['ParentID'] ) );
}
return $this->showWithEditForm( $_REQUEST, $form );
}
public function adddraft( $data, $form ) {
$this->save( $data, $form );
$draftID = $this->newDraft( $_REQUEST['ID'] );
return $this->getNewsletterEditForm( $draftID );
}
/**
* Create a new newsletter type
*/
private function newNewsletterType() {
// create a new group for the newsletter
$newGroup = new Group();
$newGroup->Title = "New mailing list";
$newGroup->Code = "new-mailing-list";
$newGroup->write();
// create the new type
$newsletterType = new NewsletterType();
$newsletterType->Title = 'New newsletter type';
$newsletterType->GroupID = $newGroup->ID;
$newsletterType->write();
// return the contents of the site tree
return $newsletterType;
}
private function newDraft( $parentID ) {
if(!$parentID || !is_numeric( $parentID)) {
$parent = DataObject::get_one("NewsletterType");
$parentID = $parent->ID;
}
if( $parentID && is_numeric( $parentID ) ) {
$newsletter = new Newsletter();
$newsletter->Status = 'Draft';
$newsletter->Title = $newsletter->Subject = 'New draft newsletter';
$newsletter->ParentID = $parentID;
$newsletter->write();
} else {
user_error( "You must first create a newsletter type before creating a draft", E_USER_ERROR );
}
return $newsletter->ID;
}
public function newmember() {
Session::clear('currentMember');
$newMemberForm = array(
"MemberForm" => $this->getMemberForm('new'),
);
if(Director::is_ajax()) {
SSViewer::setOption('rewriteHashlinks', false);
$customised = $this->customise($newMemberForm);
$result = $customised->renderWith($this->class . "_rightbottom");
$parts = split('</?form[^>]*>', $result);
echo $parts[1];
} else {
return $newMemberForm;
}
}
public function EditedMember() {
if(Session::get('currentMember'))
return DataObject::get_by_id("Member", Session::get('currentMember'));
}
public function Link($action = null) {
if(!$action) $action = "index";
return "admin/newsletter/$action/" . $this->currentPageID();
}
public function displayfilefield() {
$id = $this->urlParams['ID'];
return $this->customise( array( 'ID' => $id, "UploadForm" => $this->UploadForm() ) )->renderWith('Newsletter_RecipientImportField');
}
function UploadForm( $id = null ) {
if( !$id )
$id = $this->urlParams['ID'];
$fields = new FieldSet(
new FileField( "ImportFile", "" ),
//new HiddenField( "action_import", "", "1" ),
new HiddenField( "ID", "", $id )
);
$actions = new FieldSet(
new FormAction( "action_import", "Show contents" )
);
return new RecipientImportField_UploadForm( $this, "UploadForm", $fields, $actions );
}
}
?>

View File

@ -0,0 +1,320 @@
<?php
class UserDefinedForm extends Page {
static $add_action = "a contact form";
static $icon = "cms/images/treeicons/task";
// a list of groups that are permitted to create pages of this type.
/*static $can_create = array(
'Administrators'
);*/
static $need_permission = 'ADMIN';
static $db = array(
"EmailTo" => "Varchar",
"EmailOnSubmit" => "Boolean",
"SubmitButtonText" => "Varchar",
"OnCompleteMessage" => "HTMLText"
);
static $defaults = array(
"OnCompleteMessage" => "<p>Thanks, we've received your submission.</p>",
);
static $has_many = array(
"Fields" => "EditableFormField",
"Submissions" => "SubmittedForm"
);
protected $fields;
function getCMSFields($cms) {
$fields = parent::getCMSFields($cms);
$fields->addFieldToTab("Root.Form", new FieldEditor("Fields", "Fields", "", $this ));
$fields->addFieldToTab("Root.Submissions", new SubmittedFormReportField( "Reports", "Received Submissions", "", $this ) );
$fields->addFieldToTab("Root.Content.On complete", new HtmlEditorField( "OnCompleteMessage", "Show on completion",3,"",$this->OnCompleteMessage, $this ) );
return $fields;
}
function FilterForm() {
// Build fields
$fields = new FieldSet();
$required = array();
foreach( $this->Fields() as $field ) {
$fields->push( $field->getFilterField() );
}
// Build actions
$actions = new FieldSet(
new FormAction( "filter", "Submit" )
);
// set the name of the form
return new Form( $this, "Form", $fields, $actions );
}
/**
* Filter the submissions by the given criteria
*/
function filter( $data, $form ) {
$filterClause = array( "`SubmittedForm`.`ParentID` = '{$this->ID}'" );
$keywords = preg_split( '/\s+/', $data['FilterKeyword'] );
$keywordClauses = array();
// combine all keywords into one clause
foreach( $keywords as $keyword ) {
// escape %, \ and _ in the keyword. These have special meanings in a LIKE string
$keyword = preg_replace( '/([%_])/', '\\\\1', addslashes( $keyword ) );
$keywordClauses[] = "`Value` LIKE '%$keyword%'";
}
if( count( $keywordClauses ) > 0 ) {
$filterClause[] = "( " . implode( ' OR ', $keywordClauses ) . ")";
$searchQuery = 'keywords \'' . implode( "', '", $keywords ) . '\' ';
}
$fromDate = addslashes( $data['FilterFromDate'] );
$toDate = addslashes( $data['FilterToDate'] );
// use date objects to convert date to value expected by database
if( ereg('^([0-9]+)/([0-9]+)/([0-9]+)$', $fromDate, $parts) )
$fromDate = $parts[3] . '-' . $parts[2] . '-' . $parts[1];
if( ereg('^([0-9]+)/([0-9]+)/([0-9]+)$', $toDate, $parts) )
$toDate = $parts[3] . '-' . $parts[2] . '-' . $parts[1];
if( $fromDate ) {
$filterClause[] = "`SubmittedForm`.`Created` >= '$fromDate'";
$searchQuery .= 'from ' . $fromDate . ' ';
}
if( $toDate ) {
$filterClause[] = "`SubmittedForm`.`Created` <= '$toDate'";
$searchQuery .= 'to ' . $toDate;
}
$submittedValues = DataObject::get( 'SubmittedFormField', implode( ' AND ', $filterClause ), "", "INNER JOIN `SubmittedForm` ON `SubmittedFormField`.`ParentID`=`SubmittedForm`.`ID`" );
if( !$submittedValues || $submittedValues->Count() == 0 )
return "No matching results found";
$submissions = $submittedValues->groupWithParents( 'ParentID', 'SubmittedForm' );
if( !$submissions || $submissions->Count() == 0 )
return "No matching results found";
return $submissions->customise(
array( 'Submissions' => $submissions )
)->renderWith( 'SubmittedFormReportField_Reports' );
}
function ReportFilterForm() {
return new SubmittedFormReportField_FilterForm( $this, 'ReportFilterForm' );
}
function delete() {
// remove all the fields associated with this page
foreach( $this->Fields() as $field )
$field->delete();
parent::delete();
}
public function customFormActions( $isReadonly = false ) {
return new FieldSet( new TextField( "SubmitButtonText", "Text on submit button:", $this->SubmitButtonText ) );
}
/**
* Duplicate this UserDefinedForm page, and its form fields.
* Submissions, on the other hand, won't be duplicated.
*/
public function duplicate() {
$page = parent::duplicate();
foreach($this->Fields() as $field) {
$newField = $field->duplicate();
$newField->ParentID = $page->ID;
$newField->write();
}
return $page;
}
}
class UserDefinedForm_Controller extends Page_Controller {
function init() {
Requirements::javascript('jsparty/prototype-safe.js');
Requirements::javascript('jsparty/behaviour.js');
Requirements::javascript('mot/javascript/UserDefinedForm.js');
parent::init();
}
function Form() {
// Build fields
$fields = new FieldSet();
$required = array();
if( !$this->SubmitButtonText )
$this->SubmitButtonText = 'Submit';
foreach( $this->Fields() as $field ) {
$fields->push( $field->getFormField() );
if( $field->Required )
$required[] = $field->Name;
}
$fields->push( new HiddenField( "Referrer", "", $_SERVER['HTTP_REFERER'] ) );
// Build actions
$actions = new FieldSet(
new FormAction( "process", $this->SubmitButtonText )
);
// set the name of the form
$form = new Form( $this, "Form", $fields, $actions, new RequiredFields( $required ) );
$form->loadDataFrom($this->failover);
return $form;
}
function ReportFilterForm() {
return new SubmittedFormReportField_FilterForm( $this, 'ReportFilterForm' );
}
function process( $data, $form ) {
$submittedForm = new SubmittedForm();
$submittedForm->SubmittedBy = Member::currentUser();
$submittedForm->ParentID = $this->ID;
$submittedForm->Recipient = $this->EmailTo;
$submittedForm->write();
$values = array();
$recipientAddresses = array();
$sendCopy = false;
$submittedFields = new DataObjectSet();
foreach( $this->Fields() as $field ) {
$submittedField = new SubmittedFormField();
$submittedField->ParentID = $submittedForm->ID;
$submittedField->Name = $field->Name;
$submittedField->Title = $field->Title;
if( $field->hasMethod( 'getValueFromData' ) )
$submittedField->Value = $field->getValueFromData( $data );
else
$submittedField->Value = $data[$field->Name];
$submittedField->write();
$submittedFields->push($submittedField);
if(!empty( $data[$field->Name])){
// execute the appropriate functionality based on the form field.
switch($field->ClassName){
case "EditableEmailField" :
if($field->SendCopy){
$recipientAddresses[] = $data[$field->Name];
$sendCopy = true;
$values[$field->Title] = '<a style="white-space: nowrap" href="mailto:'.$data[$field->Name].'">'.$data[$field->Name].'</a>';
}
break;
case "EditableFileField" :
// Returns a file type which we attach to the email.
$submittedfile = $field->createSubmittedField($data[$field->Name], $submittedForm);
$file = $submittedfile->UploadedFile();
$filename = $file->getFilename();
// Attach the file if its less than 1MB, provide a link if its over.
if($file->getAbsoluteSize() < 1024*1024*1){
$attachments[] = $file;
}
// Always provide the link if present.
if($file->ID) {
$submittedField->Value = $values[$field->Title] = "<a href=\"". $filename ."\" title=\"". Director::absoluteBaseURL(). $filename. "\">Uploaded to: ". Director::absoluteBaseURL(). $filename . "</a>";
} else {
$submittedField->Value = $values[$field->Title] = "";
}
break;
}
}elseif( $field->hasMethod( 'getValueFromData' ) ) {
$values[$field->Title] = Convert::linkIfMatch($field->getValueFromData( $data ));
} else {
$values[$field->Title] = Convert::linkIfMatch($data[$field->Name]);
}
}
if( $this->EmailOnSubmit || $sendCopy ) {
$emailData = array(
"Recipient" => $this->EmailTo,
"Sender" => Member::currentUser(),
"Fields" => $submittedFields,
);
$email = new UserDefinedForm_SubmittedFormEmail($submittedFields);
$email->populateTemplate($emailData);
$email->setTo( $this->EmailTo );
$email->setSubject( $this->Title );
// add attachments to email (<1MB)
if($attachments){
foreach($attachments as $file){
$email->attachFile($filename,$filename);
}
}
$email->send();
// send to each of email fields
foreach( $recipientAddresses as $addr ) {
$email->setTo( $addr );
$email->send();
}
}
$custom = $this->customise(array(
"Content" => $this->customise( array( 'Link' => $data['Referrer'] ) )->renderWith('ReceivedFormSubmission'),
"Form" => " ",
));
return $custom->renderWith('Page');
}
}
class UserDefinedForm_SubmittedFormEmail extends Email_Template {
protected $ss_template = "SubmittedFormEmail";
protected $from = '$Sender.Email';
protected $to = '$Recipient.Email';
protected $subject = "Submission of form";
protected $data;
function __construct($values) {
parent::__construct();
$this->data = $values;
}
function Data() {
return $this->data;
}
}
?>

129
code/ReportAdmin.php Executable file
View File

@ -0,0 +1,129 @@
<?php
class ReportAdmin extends LeftAndMain {
static $subitem_class = "GrantObject";
static $template_path = null; // defaults to (project)/templates/email
public function init() {
parent::init();
Requirements::javascript(MCE_ROOT . "tiny_mce_src.js");
Requirements::javascript("jsparty/tiny_mce_improvements.js");
Requirements::javascript("jsparty/hover.js");
Requirements::javascript("jsparty/scriptaculous/controls.js");
Requirements::javascript("cms/javascript/SecurityAdmin.js");
Requirements::javascript("cms/javascript/LeftAndMain_left.js");
Requirements::javascript("cms/javascript/LeftAndMain_right.js");
Requirements::javascript("cms/javascript/CMSMain_left.js");
Requirements::javascript("cms/javascript/ReportAdmin_left.js");
Requirements::javascript("cms/javascript/ReportAdmin_right.js");
// TODO Find a better solution to integrate optional Requirements in a specific order
if(Director::fileExists("ecommerce/css/DataReportCMSMain.css")) {
Requirements::css("ecommerce/css/DataReportCMSMain.css");
}
if(Director::fileExists("ecommerce/css/DataReportCMSMain.css")) {
Requirements::javascript("ecommerce/javascript/DataReport.js");
}
if(Director::fileExists(project() . "/css/DataReportCMSMain.css")) {
Requirements::css(project() . "/css/DataReportCMSMain.css");
}
if(Director::fileExists(project() . "/css/DataReportCMSMain.css")) {
Requirements::javascript(project() . "/javascript/DataReport.js");
}
// We don't want this showing up in every ajax-response, it should always be present in a CMS-environment
if(!Director::is_ajax()) {
Requirements::javascriptTemplate("cms/javascript/tinymce.template.js", array(
"ContentCSS" => project() . "/css/editor.css",
"BaseURL" => Director::absoluteBaseURL(),
));
}
}
public function Link($action = null) {
return "admin/reports/$action";
}
public function Reports(){
$allReports= ClassInfo::subclassesFor("Report");
foreach($allReports as $report) {
if($report != 'Report') $processedReports[] = new $report();
}
$reports = new DataObjectSet($processedReports);
return $reports;
}
public function showreport($params) {
return $this->showWithEditForm( $params, $this->getReportEditForm( $params['ID'] ) );
}
protected function showWithEditForm( $params, $editForm ) {
if($params['ID']) {
Session::set('currentPage', $params['ID']);
}
if($params['OtherID']) {
Session::set('currentOtherID', $params['OtherID']);
}
if($_REQUEST['ajax']) {
SSViewer::setOption('rewriteHashlinks', false);
$result = $this->customise( array( 'EditForm' => $editForm ) )->renderWith($this->getTemplatesWithSuffix("_right"));
return $this->getLastFormIn($result);
} else {
return array();
}
}
public function EditForm() {
$id = $_REQUEST['ID'] ? $_REQUEST['ID'] : Session::get('currentPage');
$subclasses = ClassInfo::subclassesFor('Report');
foreach($subclasses as $class){
if($class != 'Report') {
$obj = new $class();
$ids[] = $obj->getOwnerID();
}
}
// bdc: do we have any subclasses?
if(sizeof($ids) > 0){
if($id && in_array($id, $ids)) return $this->getReportEditForm($id);
}
else {
return null;
}
}
public function getReportEditForm($id){
if(is_numeric($id))
$page = DataObject::get_by_id("SiteTree", $id);
if($page) $reportClass = "Report_".$page->ClassName;
if(!$reportClass)
$reportClass = $id;
$obj = new $reportClass();
$fields = $obj->getCMSFields();
$fields->push($idField = new HiddenField("ID"));
$idField->setValue($id);
//$actions = new FieldSet(new FormAction('exporttocsv', 'Export to CVS'));
$actions = new FieldSet();
$form = new Form($this, "EditForm", $fields, $actions);
return $form;
}
}
?>

334
code/SecurityAdmin.php Normal file
View File

@ -0,0 +1,334 @@
<?php
class SecurityAdmin extends LeftAndMain {
static $tree_class = "Group";
static $subitem_class = "Member";
public function init() {
// Check permissions
// if(!Member::currentUser() || !Member::currentUser()->isAdmin()) Security::permissionFailure($this);
parent::init();
Requirements::javascript("jsparty/hover.js");
Requirements::javascript("jsparty/scriptaculous/controls.js");
// needed for MemberTableField (Requirements not determined before Ajax-Call)
Requirements::javascript("sapphire/javascript/TableListField.js");
Requirements::javascript("sapphire/javascript/TableField.js");
Requirements::javascript("sapphire/javascript/ComplexTableField.js");
Requirements::javascript("cms/javascript/MemberTableField.js");
Requirements::css("jsparty/greybox/greybox.css");
Requirements::css("sapphire/css/ComplexTableField.css");
Requirements::javascript("cms/javascript/SecurityAdmin.js");
Requirements::javascript("cms/javascript/SecurityAdmin_left.js");
Requirements::javascript("cms/javascript/SecurityAdmin_right.js");
Requirements::javascript("jsparty/greybox/AmiJS.js");
Requirements::javascript("jsparty/greybox/greybox.js");
}
public function getEditForm($id) {
$record = DataObject::get_by_id("Group", $id);
if($record) {
$fields = new FieldSet(
new TabSet("Root",
new Tab("Members",
new TextField("Title", "Group name"),
$memberList = new MemberTableField(
$this,
"Members",
$record
)
),
new Tab("Permissions",
new LiteralField("", "<p>This section is for advanced users only.
See <a href=\"http://doc.silverstripe.com/doku.php?id=permissions:codes\" target=\"_blank\">this page</a>
for more information.</p>"),
new TableField(
"Permissions",
"Permission",
array("Code" => "Code", "Arg" => "Optional ID"),
array("Code" => "PermissionDropdownField", "Arg" => "TextField"),
"GroupID", $id
)
)
)
);
$memberList->setController($this);
$fields->push($idField = new HiddenField("ID"));
$idField->setValue($id);
$actions = new FieldSet(
new FormAction('addmember','Add Member')
);
$actions->push(new FormAction('save','Save'));
$form = new Form($this, "EditForm", $fields, $actions);
$form->loadDataFrom($record);
return $form;
}
}
public function AddRecordForm() {
$m = new MemberTableField(
$this,
"Members",
$this->currentPageID()
);
return $m->AddRecordForm();
}
/**
* Ajax autocompletion
*/
public function autocomplete() {
$fieldName = $this->urlParams['ID'];
$fieldVal = $_REQUEST[$fieldName];
$matches = DataObject::get("Member","$fieldName LIKE '" . addslashes($fieldVal) . "%'");
if($matches) {
$result .= "<ul>";
foreach($matches as $match) {
$data = $match->FirstName;
$data .= ",$match->Surname";
$data .= ",$match->Email";
$data .= ",$match->Password";
$result .= "<li>" . $match->$fieldName . "<span class=\"informal\">($match->FirstName $match->Surname, $match->Email)</span><span class=\"informal data\">$data</span></li>";
}
$result .= "</ul>";
return $result;
}
}
public function getmember() {
Session::set('currentMember', $_REQUEST['ID']);
SSViewer::setOption('rewriteHashlinks', false);
$result = $this->renderWith($this->class . "_rightbottom");
$parts = split('</?form[^>]*>', $result);
echo $parts[1];
}
public function MemberForm() {
$id = $_REQUEST['ID'] ? $_REQUEST['ID'] : Session::get('currentMember');
if($id)
return $this->getMemberForm($id);
}
public function getMemberForm($id) {
if($id && $id != 'new') $record = DataObject::get_one("Member", "`Member`.ID = $id");
if($record || $id == 'new') {
$fields = new FieldSet(
new HiddenField('MemberListBaseGroup', '', $this->currentPageID() )
);
if( $extraFields = $record->getCMSFields() )
foreach( $extraFields as $extra )
$fields->push( $extra );
$fields->push($idField = new HiddenField("ID"));
$fields->push($groupIDField = new HiddenField("GroupID"));
$actions = new FieldSet();
$actions->push(new FormAction('savemember','Save'));
$form = new Form($this, "MemberForm", $fields, $actions);
if($record) $form->loadDataFrom($record);
$idField->setValue($id);
$groupIDField->setValue($this->currentPageID());
return $form;
}
}
function savemember() {
$data = $_REQUEST;
$className = $this->stat('subitem_class');
$id = $_REQUEST['ID'];
if($id == 'new') $id = null;
if($id) {
$record = DataObject::get_one($className, "`$className`.ID = $id");
} else {
$record = new $className();
}
$record->update($data);
$record->ID = $id;
$record->write();
$record->Groups()->add($data['GroupID']);
FormResponse::add("reloadMemberTableField();");
return FormResponse::respond();
}
function addmember($className=null) {
$data = $_REQUEST;
unset($data['ID']);
if($className == null);
$className = $this->stat('subitem_class');
$record = new $className();
$record->update($data);
$record->write();
if($data['GroupID'])
$record->Groups()->add($data['GroupID']);
FormResponse::add("reloadMemberTableField();");
return FormResponse::respond();
}
public function removememberfromgroup() {
$groupID = $this->urlParams['ID'];
$memberID = $this->urlParams['OtherID'];
if(is_numeric($groupID) && is_numeric($memberID)) {
$member = DataObject::get_by_id('Member', $memberID);
$member->Groups()->remove($groupID);
FormResponse::add("reloadMemberTableField();");
} else {
user_error("SecurityAdmin::removememberfromgroup: Bad parameters: Group=$groupID, Member=$memberID", E_USER_ERROR);
}
return FormResponse::respond();
}
/**
* Return the entire site tree as a nested set of ULs
*/
public function SiteTreeAsUL() {
$className = "Group";
$obj = singleton($className);
// getChildrenAsUL is a flexible and complex way of traversing the tree
$siteTree = $obj->getChildrenAsUL("",
' "<li id=\"record-$child->ID\" class=\"$child->class " . ($child->Locked ? " nodelete" : "") . ' .
' ($extraArg->isCurrentPage($child) ? " current" : "") . "\">" . ' .
' "<a href=\"" . Director::link("admin", "show", $child->ID) . "\" >" . $child->Title . "</a>" ',$this);
$siteTree = "<ul id=\"sitetree\" class=\"tree unformatted\">" .
"<li id=\"record-0\" class=\"Root\">" .
"<a href=\"admin/security/show/0\" >Security groups</a>"
. $siteTree .
"</li>" .
"</ul>";
return $siteTree;
}
public function addgroup() {
$parent = $_REQUEST['ParentID'] ? $_REQUEST['ParentID'] : 0;
$p = new Group();
$p->Title = "New Group";
$p->Code = "new-group";
$p->ParentID = $parent;
$p->write();
return $this->returnItemToUser($p);
}
public function newmember() {
Session::clear('currentMember');
$newMemberForm = array(
"MemberForm" => $this->getMemberForm('new'),
);
// This should be using FormResponse ;-)
if(Director::is_ajax()) {
SSViewer::setOption('rewriteHashlinks', false);
$customised = $this->customise($newMemberForm);
$result = $customised->renderWith($this->class . "_rightbottom");
$parts = split('</?form[^>]*>', $result);
return $parts[1];
} else {
return $newMemberForm;
}
}
public function EditedMember() {
if(Session::get('currentMember'))
return DataObject::get_by_id("Member", Session::get('currentMember'));
}
public function Link($action = null) {
if(!$action) $action = "index";
return "admin/security/$action/" . $this->currentPageID();
}
public function listmembers( $baseGroup = null ) {
if( !$baseGroup )
$baseGroup = $this->urlParams['ID'];
// Debug::message( $_REQUEST['MemberListOrderByField'] );
// construct the filter and sort
if( $_REQUEST['MemberListOrderByField'] )
$sort = "`" . $_REQUEST['MemberListOrderByField'] . "`" . addslashes( $_REQUEST['MemberListOrderByOrder'] );
$whereClauses = array();
$search = addslashes( $_REQUEST['MemberListSearch'] );
if( $_REQUEST['MemberListPage'] ) {
$pageSize = 10;
$limitClause = ( $_REQUEST['MemberListPage'] ) . ", $pageSize";
}
if( !empty($_REQUEST['MemberListSearch']) )
$whereClauses[] = "( `Email`='$search' OR `FirstName`='$search' OR `Surname`='$search' )";
if( is_numeric( $_REQUEST['MemberListBaseGroup'] ) ) {
$whereClauses[] = "`GroupID`='".$_REQUEST['MemberListBaseGroup']."'";
$join = "INNER JOIN `Group_Members` ON `MemberID`=`Member`.`ID`";
}
// $_REQUEST['showqueries'] = 1;
$members = DataObject::get('Member', implode( ' AND ', $whereClauses ), $sort, $join, $limitClause );
if( is_numeric( $_REQUEST['MemberListGroup'] ) ) {
$baseMembers = new DataObjectSet();
if( $members )
foreach( $members as $member )
if( $member->inGroup( $_REQUEST['MemberListGroup'] ) )
$baseMembers->push( $member );
} else
$baseMembers = $members;
$baseMembers = null;
// user_error( $_REQUEST['MemberListBaseGroup'], E_USER_ERROR );
$memberListField = new MemberTableField(
$this,
'MemberList',
$_REQUEST['MemberListBaseGroup'],
$baseMembers,
$_REQUEST['MemberListDontShowPassword']
);
return $memberListField->renderWith('MemberList_Table');
}
}
?>

64
code/SideReport.php Executable file
View File

@ -0,0 +1,64 @@
<?php
abstract class SideReport extends Object {
abstract function records();
abstract function fieldsToShow();
abstract function title();
function getHTML() {
$records = $this->records();
$fieldsToShow = $this->fieldsToShow();
if($records) {
$result = "<ul class=\"$this->class\">\n";
foreach($records as $record) {
$result .= "<li>\n";
foreach($fieldsToShow as $fieldTitle => $fieldSource) {
$fieldName = ereg_replace('[^A-Za-z0-9]+','',$fieldTitle);
if(is_string($fieldSource)) {
$val = $record->$fieldSource;
} else {
$val = $record->val($fieldSource[0], $fieldSource[1]);
}
$result .= "<a class=\"$fieldName\" href=\"admin/show/$record->ID\">$val</a>";
}
$result .= "\n</li>\n";
}
$result .= "</ul>\n";
} else {
$result = 'The '.$this->title().' report is empty.';
}
return $result;
}
}
class SideReport_EmptyPages extends SideReport {
function title() {
return "Empty pages";
}
function records() {
return DataObject::get("SiteTree", "Content = '' OR Content IS NULL OR Content LIKE '<p></p>' OR Content LIKE '<p>&nbsp;</p>'", "Title");
}
function fieldsToShow() {
return array(
"Title" => array("NestedTitle", array("2")),
);
}
}
class SideReport_RecentlyEdited extends SideReport {
function title() {
return "Pages edited in the last 2 weeks";
}
function records() {
return DataObject::get("SiteTree", "`SiteTree`.LastEdited > NOW() - INTERVAL 14 DAY", "`SiteTree`.`LastEdited` DESC");
}
function fieldsToShow() {
return array(
"Title" => array("NestedTitle", array("2")),
);
}
}
?>

87
code/StaticExporter.php Executable file
View File

@ -0,0 +1,87 @@
<?php
/**
* This class lets you export a static copy of your site.
* It creates a huge number of folders each containing an index.html file.
* This preserves the URL naming format.
*/
class StaticExporter extends Controller {
function init() {
parent::init();
if(!$this->can('AdminCMS')) {
$messageSet = array(
'default' => "Enter your email address and password to access the CMS.",
'alreadyLoggedIn' => "I'm sorry, but you can't access that part of the CMS. If you want to log in as someone else, do so below",
'logInAgain' => "You have been logged out of the CMS. If you would like to log in again, enter a username and password below.",
);
Security::permissionFailure($this, $messageSet);
exit;
}
}
function Link($action = null) {
return "StaticExporter/$action";
}
function index() {
echo "<h1>Static exporter</h1>";
echo $this->StaticExportForm()->forTemplate();
}
function StaticExportForm() {
return new Form($this, 'StaticExportForm', new FieldSet(
new TextField('folder', 'Folder to export to'),
new TextField('baseurl', 'Base URL')
), new FieldSet(
new FormAction('export', 'Export to that folder')
));
}
function export() {
if($_REQUEST['baseurl']) {
$base = $_REQUEST['baseurl'];
if(substr($base,-1) != '/') $base .= '/';
Director::setBaseURL($base);
}
$folder = $_REQUEST['folder'];
if($folder && file_exists($folder)) {
$pages = DataObject::get("SiteTree");
foreach($pages as $page) {
$subfolder = "$folder/$page->URLSegment";
$contentfile = "$folder/$page->URLSegment/index.html";
echo "<li>$page->URLSegment/index.html";
// Make the folder
if(!file_exists($subfolder)) {
mkdir($subfolder);
chmod($subfolder,02775);
}
// Run the page
Requirements::clear();
$controllerClass = "{$page->class}_Controller";
if(class_exists($controllerClass)) {
$controller = new $controllerClass($page);
$pageContent = $controller->run( array() );
// Write to file
if($fh = fopen($contentfile, 'w')) {
fwrite($fh, $pageContent);
fclose($fh);
}
}
}
} else {
echo "Please specify a folder that exists";
}
}
}
?>

133
code/ThumbnailStripField.php Executable file
View File

@ -0,0 +1,133 @@
<?php
/**
* Provides a strip of thumbnails showing all of the images in the system.
* It will be tied to a 'parent field' that will provide it with a filter by which to reduce the number
* of thumbnails displayed.
*/
class ThumbnailStripField extends FormField {
protected $parentField;
protected $updateMethod;
function __construct($name, $parentField, $updateMethod = "getimages") {
$this->parentField = $parentField;
$this->updateMethod = $updateMethod;
parent::__construct($name);
}
function ParentField() {
return $this->form->FormName() . '_' . $this->parentField;
}
function FieldHolder() {
Requirements::javascript('cms/javascript/ThumbnailStripField.js');
return $this->renderWith('ThumbnailStripField');
}
function Images() {
//return DataObject::get("Image", "Paretn);
}
function UpdateMethod() {
return $this->updateMethod;
}
/**
* Populate the Thumbnail strip field, by looking for a folder,
* and the descendants of this folder.
*/
function getimages() {
$result = '';
$folder = DataObject::get_by_id("Folder", $_GET['folderID']);
if( !$folder )
return 'This is not a folder';
$folderList = $folder->getDescendantIDList("Folder");
array_unshift($folderList, $folder->ID);
$images = DataObject::get("Image", "ParentID IN (" . implode(', ', $folderList) . ")");
if($images) {
$result .= '<ul>';
foreach($images as $image) {
$thumbnail = $image->getFormattedImage('StripThumbnail');
// Constrain the output image to a 600x600 square. This is passed to the destwidth/destheight in the class, which are then used to
// set width & height properties on the <img> tag inserted into the CMS. Resampling is done after save
$width = $image->Width;
$height = $image->Height;
if($width > 600) {
$height *= (600/$width);
$width = 600;
}
if($height > 600) {
$width *= (600/$height);
$height = 600;
}
$result .=
'<li>' .
'<a href=" ' . $image->Filename . '">' .
'<img class="destwidth=' . round($width) . ',destheight=' . round($height) . '" src="'. $thumbnail->URL .'" alt="' . $image->Title . '" title="' . $image->Title . '" />' .
'</a>' .
'</li>';
}
$result .= '</ul>';
}else{
$result = "<h2> No images found in ". $folder->Title. "</h2>";
}
return $result;
}
function getflash() {
$folder = DataObject::get_by_id("Folder", $_GET['folderID']);
if( !$folder )
return 'This is not a folder';
$folderList = $folder->getDescendantIDList("Folder");
array_unshift($folderList, $folder->ID);
$width = Image::$strip_thumbnail_width - 10;
$height = Image::$strip_thumbnail_height - 10;
$flashObjects = DataObject::get("File", "ParentID IN (" . implode(', ', $folderList) . ") AND Filename LIKE '%.swf'");
if($flashObjects) {
$result .= '<ul>';
foreach($flashObjects as $flashObject) {
// doesn't work well because we can't stop/mute flash-files, AND IE does not bubble javascript-events
// over a flash-object grml
// $result .= <<<HTML
//<a href="$flashObject->URL">
// <object type="application/x-shockwave-flash" data="$flashObject->URL" width="$width" height="$height">
// <param name="movie" value="$flashObject->URL" />
// </object>
//</a>
//HTML;
$mceRoot = MCE_ROOT .
$result .= <<<HTML
<li>
<a href="$flashObject->URL">
<img src="{$mceRoot}themes/advanced/images/spacer_flash.jpg" alt="spacer" width="$width" height="$height" />
<br />
$flashObject->Name
</a>
</li>
HTML;
}
$result .= '</ul>';
}
return $result;
}
}
?>

View File

@ -0,0 +1,397 @@
<?php
/**
* Akismet anti-comment spam service
*
* The class in this package allows use of the {@link http://akismet.com Akismet} anti-comment spam service in any PHP5 application.
*
* This service performs a number of checks on submitted data and returns whether or not the data is likely to be spam.
*
* Please note that in order to use this class, you must have a vaild {@link http://wordpress.com/api-keys/ WordPress API key}. They are free for non/small-profit types and getting one will only take a couple of minutes.
*
* For commercial use, please {@link http://akismet.com/commercial/ visit the Akismet commercial licensing page}.
*
* Please be aware that this class is PHP5 only. Attempts to run it under PHP4 will most likely fail.
*
* See the Akismet class documentation page linked to below for usage information.
*
* @package Akismet
* @author Alex Potsides, {@link http://www.achingbrain.net http://www.achingbrain.net}
* @version 0.1
* @copyright Alex Potsides, {@link http://www.achingbrain.net http://www.achingbrain.net}
* @license http://www.gnu.org/licenses/gpl.html GNU General Public License
*/
/**
* The Akismet PHP5 Class
*
* This class takes the functionality from the Akismet WordPress plugin written by {@link http://photomatt.net/ Matt Mullenweg} and allows it to be integrated into any PHP5 application or website.
*
* The original plugin is {@link http://akismet.com/download/ available on the Akismet website}.
*
* <b>Usage:</b>
* <code>
* $akismet = new Akismet('http://www.example.com/blog/', 'aoeu1aoue');
* $akismet->setCommentAuthor($name);
* $akismet->setCommentAuthorEmail($email);
* $akismet->setCommentAuthorURL($url);
* $akismet->setCommentContent($comment);
* $akismet->setPermalink('http://www.example.com/blog/alex/someurl/');
* if($akismet->isCommentSpam())
* // store the comment but mark it as spam (in case of a mis-diagnosis)
* else
* // store the comment normally
* </code>
*
* @package akismet
* @name Akismet
* @version 0.2
* @author Alex Potsides
* @link http://www.achingbrain.net/
*/
class Akismet
{
private $version = '0.2';
private $wordPressAPIKey;
private $blogURL;
private $comment;
private $apiPort;
private $akismetServer;
private $akismetVersion;
// This prevents some potentially sensitive information from being sent accross the wire.
private $ignore = array('HTTP_COOKIE',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED_HOST',
'HTTP_MAX_FORWARDS',
'HTTP_X_FORWARDED_SERVER',
'REDIRECT_STATUS',
'SERVER_PORT',
'PATH',
'DOCUMENT_ROOT',
'SERVER_ADMIN',
'QUERY_STRING',
'PHP_SELF' );
/**
* @throws Exception An exception is thrown if your API key is invalid.
* @param string Your WordPress API key.
* @param string $blogURL The URL of your blog.
*/
public function __construct($blogURL, $wordPressAPIKey)
{
$this->blogURL = $blogURL;
$this->wordPressAPIKey = $wordPressAPIKey;
// Set some default values
$this->apiPort = 80;
$this->akismetServer = 'rest.akismet.com';
$this->akismetVersion = '1.1';
// Start to populate the comment data
$this->comment['blog'] = $blogURL;
$this->comment['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
$this->comment['referrer'] = $_SERVER['HTTP_REFERER'];
// This is necessary if the server PHP5 is running on has been set up to run PHP4 and
// PHP5 concurently and is actually running through a separate proxy al a these instructions:
// http://www.schlitt.info/applications/blog/archives/83_How_to_run_PHP4_and_PHP_5_parallel.html
// and http://wiki.coggeshall.org/37.html
// Otherwise the user_ip appears as the IP address of the PHP4 server passing the requests to the
// PHP5 one...
$this->comment['user_ip'] = $_SERVER['REMOTE_ADDR'] != getenv('SERVER_ADDR') ? $_SERVER['REMOTE_ADDR'] : getenv('HTTP_X_FORWARDED_FOR');
// Check to see if the key is valid
$response = $this->http_post('key=' . $this->wordPressAPIKey . '&blog=' . $this->blogURL, $this->akismetServer, '/' . $this->akismetVersion . '/verify-key');
if($response[1] != 'valid')
{
// Whoops, no it's not. Throw an exception as we can't proceed without a valid API key.
throw new Exception('Invalid API key. Please obtain one from http://wordpress.com/api-keys/');
}
}
private function http_post($request, $host, $path)
{
$http_request = "POST " . $path . " HTTP/1.1\r\n";
$http_request .= "Host: " . $host . "\r\n";
$http_request .= "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n";
$http_request .= "Content-Length: " . strlen($request) . "\r\n";
$http_request .= "User-Agent: Akismet PHP5 Class " . $this->version . " | Akismet/1.11\r\n";
$http_request .= "\r\n";
$http_request .= $request;
$socketWriteRead = new SocketWriteRead($host, $this->apiPort, $http_request);
$socketWriteRead->send();
return explode("\r\n\r\n", $socketWriteRead->getResponse(), 2);
}
// Formats the data for transmission echo $sql;
private function getQueryString()
{
foreach($_SERVER as $key => $value)
{
if(!in_array($key, $this->ignore))
{
if($key == 'REMOTE_ADDR')
{
$this->comment[$key] = $this->comment['user_ip'];
}
else
{
$this->comment[$key] = $value;
}
}
}
$query_string = '';
foreach($this->comment as $key => $data)
{
@$query_string .= $key . '=' . urlencode(stripslashes($data)) . '&';
}
return $query_string;
}
/**
* Tests for spam.
*
* Uses the web service provided by {@link http://www.akismet.com Akismet} to see whether or not the submitted comment is spam. Returns a boolean value.
*
* @return bool True if the comment is spam, false if not
*/
public function isCommentSpam()
{
$response = $this->http_post($this->getQueryString(), $this->wordPressAPIKey . '.rest.akismet.com', '/' . $this->akismetVersion . '/comment-check');
return ($response[1] == 'true');
}
/**
* Submit spam that is incorrectly tagged as ham.
*
* Using this function will make you a good citizen as it helps Akismet to learn from its mistakes. This will improve the service for everybody.
*/
public function submitSpam()
{
$this->http_post($this->getQueryString(), $this->wordPressAPIKey . '.' . $this->akismetServer, '/' . $this->akismetVersion . '/submit-spam');
}
/**
* Submit ham that is incorrectly tagged as spam.
*
* Using this function will make you a good citizen as it helps Akismet to learn from its mistakes. This will improve the service for everybody.
*/
public function submitHam()
{
$this->http_post($this->getQueryString(), $this->wordPressAPIKey . '.' . $this->akismetServer, '/' . $this->akismetVersion . '/submit-ham');
}
/**
* To override the user IP address when submitting spam/ham later on
*
* @param string $userip An IP address. Optional.
*/
public function setUserIP($userip)
{
$this->comment['user_ip'] = $userip;
}
/**
* To override the referring page when submitting spam/ham later on
*
* @param string $referrer The referring page. Optional.
*/
public function setReferrer($referrer)
{
$this->comment['referrer'] = $referrer;
}
/**
* A permanent URL referencing the blog post the comment was submitted to.
*
* @param string $permalink The URL. Optional.
*/
public function setPermalink($permalink)
{
$this->comment['permalink'] = $permalink;
}
/**
* The type of comment being submitted.
*
* May be blank, comment, trackback, pingback, or a made up value like "registration" or "wiki".
*/
public function setCommentType($commentType)
{
$this->comment['comment_type'] = $commentType;
}
/**
* The name that the author submitted with the comment.
*/
public function setCommentAuthor($commentAuthor)
{
$this->comment['comment_author'] = $commentAuthor;
}
/**
* The email address that the author submitted with the comment.
*
* The address is assumed to be valid.
*/
public function setCommentAuthorEmail($authorEmail)
{
$this->comment['comment_author_email'] = $authorEmail;
}
/**
* The URL that the author submitted with the comment.
*/
public function setCommentAuthorURL($authorURL)
{
$this->comment['comment_author_url'] = $authorURL;
}
/**
* The comment's body text.
*/
public function setCommentContent($commentBody)
{
$this->comment['comment_content'] = $commentBody;
}
/**
* Defaults to 80
*/
public function setAPIPort($apiPort)
{
$this->apiPort = $apiPort;
}
/**
* Defaults to rest.akismet.com
*/
public function setAkismetServer($akismetServer)
{
$this->akismetServer = $akismetServer;
}
/**
* Defaults to '1.1'
*/
public function setAkismetVersion($akismetVersion)
{
$this->akismetVersion = $akismetVersion;
}
}
/**
* Utility class used by Akismet
*
* This class is used by Akismet to do the actual sending and receiving of data. It opens a connection to a remote host, sends some data and the reads the response and makes it available to the calling program.
*
* The code that makes up this class originates in the Akismet WordPress plugin, which is {@link http://akismet.com/download/ available on the Akismet website}.
*
* N.B. It is not necessary to call this class directly to use the Akismet class. This is included here mainly out of a sense of completeness.
*
* @package akismet
* @name SocketWriteRead
* @version 0.1
* @author Alex Potsides
* @link http://www.achingbrain.net/
*/
class SocketWriteRead
{
private $host;
private $port;
private $request;
private $response;
private $responseLength;
private $errorNumber;
private $errorString;
/**
* @param string $host The host to send/receive data.
* @param int $port The port on the remote host.
* @param string $request The data to send.
* @param int $responseLength The amount of data to read. Defaults to 1160 bytes.
*/
public function __construct($host, $port, $request, $responseLength = 1160)
{
$this->host = $host;
$this->port = $port;
$this->request = $request;
$this->responseLength = $responseLength;
$this->errorNumber = 0;
$this->errorString = '';
}
/**
* Sends the data to the remote host.
*
* @throws An exception is thrown if a connection cannot be made to the remote host.
*/
public function send()
{
$this->response = '';
$fs = fsockopen($this->host, $this->port, $this->errorNumber, $this->errorString, 3);
if($this->errorNumber != 0)
{
throw new Exception('Error connecting to host: ' . $this->host . ' Error number: ' . $this->errorNumber . ' Error message: ' . $this->errorString);
}
if($fs !== false)
{
@fwrite($fs, $this->request);
while(!feof($fs))
{
$this->response .= fgets($fs, $this->responseLength);
}
fclose($fs);
}
}
/**
* Returns the server response text
*
* @return string
*/
public function getResponse()
{
return $this->response;
}
/**
* Returns the error number
*
* If there was no error, 0 will be returned.
*
* @return int
*/
public function getErrorNumner()
{
return $this->errorNumber;
}
/**
* Returns the error string
*
* If there was no error, an empty string will be returned.
*
* @return string
*/
public function getErrorString()
{
return $this->errorString;
}
}
?>

144
code/sitefeatures/PageComment.php Executable file
View File

@ -0,0 +1,144 @@
<?php
class PageComment extends DataObject {
static $db = array(
"Name" => "Varchar",
"Comment" => "Text",
"IsSpam" => "Boolean",
);
static $has_one = array(
"Parent" => "SiteTree",
);
static $casting = array(
"RSSTitle" => "Varchar",
);
// Number of comments to show before paginating
static $comments_per_page = 10;
/**
* Return a link to this comment
* @return string link to this comment.
*/
function Link() {
return $this->Parent()->Link();
}
function DeleteLink() {
if(Permission::check('CMS_ACCESS_CMSMain')) {
return "PageComment/deletecomment/$this->ID";
}
}
function deletecomment() {
if(Permission::check('CMS_ACCESS_CMSMain')) {
$comment = DataObject::get_by_id("PageComment", $this->urlParams['ID']);
if($comment) {
$comment->delete();
}
}
if(Director::is_ajax()) {
echo "";
} else {
Director::redirectBack();
}
}
function SpamLink() {
$member = Member::currentUser();
if(SSAkismet::isEnabled() && Permission::check('CMS_ACCESS_CMSMain') && !$this->getField('IsSpam')) {
return "PageComment/reportspam/$this->ID";
}
}
function HamLink() {
$member = Member::currentUser();
if(SSAkismet::isEnabled() && Permission::check('CMS_ACCESS_CMSMain') && $this->getField('IsSpam')) {
return "PageComment/reportham/$this->ID";
}
}
function SpamClass() {
if($this->getField('IsSpam')) {
return 'spam';
} else {
return 'notspam';
}
}
function reportspam() {
if(SSAkismet::isEnabled() && Permission::check('CMS_ACCESS_CMSMain')) {
$comment = DataObject::get_by_id("PageComment", $this->urlParams['ID']);
if($comment) {
try {
$akismet = new SSAkismet();
$akismet->setCommentAuthor($comment->getField('Name'));
$akismet->setCommentContent($comment->getField('Comment'));
$akismet->submitSpam();
} catch (Exception $e) {
// Akismet didn't work, most likely the service is down.
}
if(SSAkismet::getSaveSpam()) {
$comment->setField('IsSpam', true);
$comment->write();
} else {
$comment->delete();
}
}
}
if(Director::is_ajax()) {
if(SSAkismet::getSaveSpam()) {
echo $comment->renderWith('PageCommentInterface_singlecomment');
} else {
echo '';
}
} else {
Director::redirectBack();
}
}
function reportham() {
if(SSAkismet::isEnabled() && Permission::check('CMS_ACCESS_CMSMain')) {
$comment = DataObject::get_by_id("PageComment", $this->urlParams['ID']);
if($comment) {
try {
$akismet = new SSAkismet();
$akismet->setCommentAuthor($comment->getField('Name'));
$akismet->setCommentContent($comment->getField('Comment'));
$akismet->submitHam();
} catch (Exception $e) {
// Akismet didn't work, most likely the service is down.
}
$comment->setField('IsSpam', false);
$comment->write();
}
}
if(Director::is_ajax()) {
echo $comment->renderWith('PageCommentInterface_singlecomment');
} else {
Director::redirectBack();
}
}
function RSSTitle() {
return "Comment by '$this->Name' on " . $this->Parent()->Title;
}
function rss() {
$rss = new RSSFeed(DataObject::get("PageComment", "ParentID > 0", "Created DESC", "", 10), "home/", "Page comments", "", "RSSTitle", "Comment", "Name");
$rss->outputToBrowser();
}
}
?>

View File

@ -0,0 +1,121 @@
<?php
/**
* Represents an interface for viewing and adding page comments
* Create one, passing the page discussed to the constructor. It can then be
* inserted into a template.
*/
class PageCommentInterface extends ViewableData {
protected $controller, $methodName, $page;
/**
* Create a new page comment interface
* @param controller The controller that the U
* @param methodName The method to return this PageCommentInterface object
* @param page The page that we're commenting on
*/
function __construct($controller, $methodName, $page) {
$this->controller = $controller;
$this->methodName = $methodName;
$this->page = $page;
}
function forTemplate() {
return $this->renderWith('PageCommentInterface');
}
function PostCommentForm() {
Requirements::javascript('jsparty/behaviour.js');
Requirements::javascript('jsparty/prototype.js');
Requirements::javascript('jsparty/scriptaculous/effects.js');
Requirements::javascript('cms/javascript/PageCommentInterface.js');
$form = new PageCommentInterface_Form($this->controller, $this->methodName . ".PostCommentForm", new FieldSet(
new HiddenField("ParentID", "ParentID", $this->page->ID),
new TextField("Name", "Your name"),
new TextareaField("Comment", "Comments")
), new FieldSet(
new FormAction("postcomment", "Post")
));
$form->loadDataFrom(array(
"Name" => Cookie::get("PageCommentInterface_Name"),
));
return $form;
}
function Comments() {
// Comment limits
if(isset($_GET['commentStart'])) {
$limit = (int)$_GET['commentStart'].",".PageComment::$comments_per_page;
} else {
$limit = "0,".PageComment::$comments_per_page;
}
if(isset($_GET['showspam'])) {
$comments = DataObject::get("PageComment", "ParentID = '" . Convert::raw2sql($this->page->ID) . "'", "Created DESC", "", $limit);
} else {
$comments = DataObject::get("PageComment", "ParentID = '" . Convert::raw2sql($this->page->ID) . "' AND IsSpam = 0", "Created DESC", "", $limit);
}
if(is_null($comments)) {
return;
}
// This allows us to use the normal 'start' GET variables as well (In the weird circumstance where you have paginated comments AND something else paginated)
$comments->setPaginationGetVar('commentStart');
return $comments;
}
}
class PageCommentInterface_Form extends Form {
function postcomment($data) {
// Spam filtering
if(SSAkismet::isEnabled()) {
try {
$akismet = new SSAkismet();
$akismet->setCommentAuthor($data['Name']);
$akismet->setCommentContent($data['Comment']);
if($akismet->isCommentSpam()) {
if(SSAkismet::getSaveSpam()) {
$comment = Object::create('PageComment');
$this->saveInto($comment);
$comment->setField("IsSpam", true);
$comment->write();
}
echo "<b>Spam detected!!</b><br /><br />";
echo "If you believe this was in error, please email ";
echo ereg_replace("@", " _(at)_", Email::getAdminEmail());
echo ".<br /><br />The message you posted was:<br /><br />";
echo $data['Comment'];
return;
}
} catch (Exception $e) {
// Akismet didn't work, continue without spam check
}
}
Cookie::set("PageCommentInterface_Name", $data['Name']);
$comment = Object::create('PageComment');
$this->saveInto($comment);
$comment->IsSpam = false;
$comment->write();
if(Director::is_ajax()) {
echo $comment->renderWith('PageCommentInterface_singlecomment');
} else {
Director::redirectBack();
}
}
}
?>

View File

@ -0,0 +1,26 @@
<?php
class SSAkismet extends Akismet {
private static $apiKey;
private static $saveSpam = false;
static function setAPIKey($key) {
self::$apiKey = $key;
}
static function isEnabled() {
return (self::$apiKey != null) ? true : false;
}
static function setSaveSpam($save = true) {
SSAkismet::$saveSpam = $save;
}
static function getSaveSpam() {
return SSAkismet::$saveSpam;
}
public function __construct() {
parent::__construct(Director::absoluteBaseURL(), self::$apiKey);
}
}
?>

236
css/GenericDataAdmin.css Executable file
View File

@ -0,0 +1,236 @@
.right form div.tab {
padding-top: 1em;
}
form#Form_EditForm #Form_ExportForm fieldset {
height: auto;
}
.leftcolumn {
margin: 0;
width: 46%;
float: left;
}
.rightcolumn {
width: 46%;
margin: 0;
float: right;
clear: right;
}
.tab div.groupfield, .tab div.groupfield div.groupfield {
margin: 1.2em 0;
border: 1px solid #ffffff;
}
.tab div.field {
margin: .3em 0;
}
#left {
width:335px
}
#LeftPane {
background-color: #FFF;
border-left: 1px #ccc solid;
padding: 0;
}
/* Generic form styling */
#LeftPane h3 {
font-size:1.2em;
letter-spacing:1px;
color:#4874C6;
}
#LeftPane form label.left {
font-size:1.1em;
font-weight:bold;
float:left;
width:100px;
padding-right:3px;
}
#LeftPane form div {
clear:left;
}
#LeftPane form div.field {
margin-bottom:5px;
}
#LeftPane form input {
padding:2px;
}
#LeftPane form input.text,
#LeftPane form select {
padding:2px;
}
#LeftPane form input.text {
width:145px;
}
#LeftPane form div.field select {
width:152px;
}
#LeftPane #AddForm {
padding: 5px 10px;
}
#Search_holder {
overflow: auto;
}
#Search_holder .ToggleAdvancedSearchFields {
margin: 3px 0;
}
#Search_holder .Actions {
margin: 5px 0;
}
#Search_holder h3 {
margin-bottom: 8px;
}
#LeftPane #Form_CreationForm {
margin: 0;
padding: 0;
}
#LeftPane #Form_CreationForm p.Actions {
margin-left: 1em;
}
#SearchForm_holder {
overflow: auto; /* TODO */
padding: 5px 10px;
}
#SearchForm_holder #DateRange .calendardate,
#SearchForm_holder #DateRange label{
float:left;
clear:none;
padding:1px;
}
#SearchForm_holder #DateRange input {
width:65px;
}
/* Result list layout */
div.ResultList {
padding: 1em;
}
div.ResultList ul {
margin: 0;
padding: 0;
list-style: none;
background:#eee;
}
div.ResultList li {
padding: .1em;
}
div.ResultList ul ul {
margin-left: 2em;
}
div.ResultList li.odd {
background:#f5f5f5;
}
div.ResultList li.even {
background:#eee;
}
/* Result table layout */
table.ResultTable table tbody tr.odd {
background:#f5f5f5;
}
table.ResultTable table tbody tr td {
padding:2px;
}
table.ResultTable {
padding: 1em;
width: 100%;
}
table.ResultTable * {
border:0;
font-size:1em;
}
table.ResultTable td {
padding: 3px;
}
table.ResultTable thead tr {
background:#eee;
}
table.ResultTable thead tr td {
font-weight:bold;
letter-spacing:1px;
font-size:1.1em;
}
table.ResultTable tbody tr {
background:#eee;
}
table.ResultTable tbody tr.even {
background:#f0f8ff;
}
table.ResultTable tbody tr.odd {
background:#fff;
}
table.ResultTable tbody tr td {
font-size:1em;
}
#Search_holder table.ResultTable tbody tr {
cursor: pointer;
}
#Form_export_action_export {
margin-top: 10px;
}
#Form_EditForm_GenericDataStatus {
position: absolute;
z-index: 500;
top: -70px;
right: -10px;
text-align: right;
width: 150px;
height: 38px;
padding: 10px;
font-size: 14px;
font-weight: bold;
border: 1px solid #cc9;
color: #660;
background-color: #F9F9E3;
}
.clear:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.clear {display: inline-block;}
/* Hides from IE-mac \*/
* html .clear {height: 1%;}
.clear {display: block;}
/* End hide from IE-mac */

146
css/Image_iframe.css Executable file
View File

@ -0,0 +1,146 @@
@import url("typography.css");
html,body {
padding: 0;
margin: 0;
border-style: none;
height: 100%;
overflow: hidden;
}
form {
margin: 0; padding: 0;
}
form fieldset{
padding: 0;
margin: 0;
border-style: none;
}
h2 {
margin: 0;
font-size: 1.4em;
}
/**
* Selection Groups
*/
.SelectionGroup {
padding: 0;
margin: 10px 0 0 0;
}
.SelectionGroup li {
list-style-type: none;
margin: 0;
}
.SelectionGroup li input.selector {
width: 20px;
}
.SelectionGroup li div.field {
display: none;
}
.SelectionGroup li.selected div.field {
margin-left: 30px;
display: block;
margin-bottom: 1em;
}
.SelectionGroup li.selected label.selector {
font-weight: bold;
}
/**
* TreeDropdownField stying
*/
div.TreeDropdownField {
width: 241px;
padding: 0;
}
html>body div.TreeDropdownField {
position:relative;
}
div.TreeDropdownField span.items {
display: block;
height: 100%;
border: 1px #7f9db9 solid;
cursor: pointer;
width: 220px;
float: left;
padding-top: 2px;
padding-bottom: 2px;
background-color: white;
}
div.TreeDropdownField div.tree_holder {
clear: left;
cursor: default;
border: 1px black solid;
margin: 0;
height: 180px;
overflow: auto;
background-color: white;
/**
* HACK IE6, see http://www.hedgerwow.com/360/bugs/css-select-free.html
*/
position:absolute;
z-index:10;
width:240px;/*must have for any value*/
}
html>body div.TreeDropdownField div.tree_holder {
top: 20px;
left: 0px;
z-index: 1000;
}
/**
* HACK IE6, see http://www.hedgerwow.com/360/bugs/css-select-free.html
*/
div.TreeDropdownField div.tree_holder iframe {
display:none;/* IE5*/
display/**/:block;/* IE5*/
position:absolute;
top:0;
left:0;
z-index:-1;
filter:mask();
width:180px; /*must have for any big value*/
height:200px/*must have for any big value*/;
border: 0;
}
div.TreeDropdownField a.editLink {
border-width: 1px 1px 1px 0;
background: url(../../sapphire/images/TreeDropdownField_button.gif) left top no-repeat;
width: 19px;
height: 21px;
margin: 0;
padding: 0;
float: left;
clear: right;
z-index: 0;
overflow: hidden;
}
.Actions {
text-align: right;
margin: 0;
position: absolute;
right: 5px;
bottom: 5px;
}
.mainblock {
float: left;
border: 1px #CCC solid;
padding: 5px;
margin-right: 5px;
height: 120px;
position: relative;
}

44
css/LeftAndMain_printable.css Executable file
View File

@ -0,0 +1,44 @@
* {
font-family: Helvetica, Verdana, Geneva, Arial, Helvetica, sans-serif;
}
h3 {
margin-top: 2em;
}
form .field {
margin-bottom: 1em;
}
form label.left {
float: left;
width: 15em;
font-weight: bold;
}
form div.field {
/*margin-left: 10em;*/
}
form .inlineformaction,
form .inlineformaction_readonly,
form .action,
form .action_readonly
{
display: none !important;
}
.pageStatusMessage {
position: absolute;
right: 2em;
top: 2em;
border: 1px #777 solid;
}
.TableListField table.data th {
background: #eee;
}
.TableListField table.data {
width: 100%;
border: 1px #aaa solid;
}

404
css/cms_left.css Normal file
View File

@ -0,0 +1,404 @@
#TreeActions {
background-color: #EEE;
border-bottom: 1px #CCC solid;
padding: 0;
margin: 0;
/* height: 23px; */
float: left;
width: 100%;
}
#TreeActions li.action {
float: left;
margin: 0;
padding: 0;
padding-right: 7px;
list-style-type: none;
}
#TreeActions li.action a {
padding: 0.3em;
display: block;
}
#TreeActions li.action a:hover {
background-color: #CCC;
}
#TreeActions li.selected a {
background-color: #CCC;
}
#left form.actionparams {
background-color: #CCC;
border-bottom: 1px #EEE solid;
margin: 0;
padding: 0.5em;
}
#left form.actionparams select {
width: 75%;
margin: 0;
}
#left form.actionparms input.action {
width: 5%;
margin: 0;
}
/**
* Selection Groups
*/
#left .SelectionGroup {
padding: 0;
margin: 0;
display: block;
}
#left .SelectionGroup li {
list-style-type: none;
clear: left;
padding: 0;
margin: 0;
border: 1px #CCC solid;
}
#left .SelectionGroup li input {
width: 20px;
float: left;
margin: 0;
}
#left .SelectionGroup li div.field {
display: none;
font-size: 1em;
}
#left .SelectionGroup li label {
margin: 0 0 0 2px;
padding: 0;
display: block;
height: 1.2em;
width: auto;
float: none;
}
#left .SelectionGroup li.selected div.field {
margin-left: 23px;
display: block;
margin-bottom: 1em;
}
#left div.field select {
width: 100%;
}
#left .SelectionGroup li.selected label.selector {
font-weight: bold;
}
ul.tree span.a {
cursor: pointer;
}
ul.tree span.a.over {
background-color: #FFFFBB;
/* these push the highlight out to the left of the window */
margin-left: -100px;
padding-left: 100px;
background-position: 100px 50%;
}
ul.tree span.a.current {
font-weight: bold;
background-color: #EEEEFF;
border-top: 1px #CCCCFF solid;
border-bottom: 1px #CCCCFF solid;
/* these push the highlight out to the left of the window */
margin-left: -100px;
padding-left: 100px;
background-position: 100px 50%;
/*position: relative;*/
}
/*
ul.tree span.a.current span#currentNodeActions {
position: absolute;
right: 0;
top: 0;
background-color: white;
padding: 0 3px;
}
*/
ul.tree span.a.loading span.b span.c a {
background-image: url(../images/network-save.gif) !important;
margin-left: -3px;
padding-left: 21px;
background-position : 2px 2px;
}
ul.tree.multiselect span.a span.b a {
background-image: url(../images/tickbox-unticked.gif) !important;
}
ul.tree.multiselect span.a.nodelete span.b a {
background-image: url(../images/tickbox-canttick.gif) !important;
}
ul.tree.multiselect span.a.selected span.b span.c a {
background-image: url(../images/tickbox-ticked.gif) !important;
}
ul.tree.multiselect li.selected ul span.a a {
background-image: url(../images/tickbox-greyticked.gif) !important;
}
/*
ul.tree.multiselect li.selected ul {
display: none;
}
*/
/* Span-B: Plus/Minus icon */
ul.tree.multiselect li.selected span.a.children span.b, ul.tree.multiselect li.selected span.a.unexpanded span.b {
background-image: none;
cursor: default;
}
ul.tree span.a a.disabled {
color: #999;
cursor: normal;
}
ul.tree span.a a.disabled * {
padding: 0;
background: none;
color: #999;
cursor: normal;
}
ul.tree .deleted {
color: red;
text-decoration: line-through;
}
/* Created on stage, never published */
ul.tree ins,
#publication_key ins {
color: orange;
padding-left : 16px;
background-image : url(../../sapphire/images/NewOnStage.png);
background-position : 0px 1px;
background-repeat : no-repeat;
text-decoration : none;
}
/* Deleated on stage */
ul.tree del,
#publication_key del {
color: red;
padding-left : 16px;
background-image : url(../../sapphire/images/TrashedOnStage.png);
background-repeat : no-repeat;
}
ul.tree span.modified,
#publication_key span.modified {
color: green;
padding-left : 16px;
background-position : 1px 0px;
background-image : url(../../sapphire/images/EditedOnStage.png);
background-repeat : no-repeat;
text-decoration : none;
}
/**
* Side tabs
*/
#SideTabs {
float: left;
margin: 0;
padding: 0;
width: 18px;
height: 80%;
}
#SideTabs li {
clear: left;
width: 16px;
text-indent: -100em;
height: 70px;
overflow: hidden;
margin: 5px 0 5px 2px;
background-repeat: no-repeat;
cursor: pointer;
}
#SideTabs li#sidetab_versions { background-image: url(../images/sidetabs/versions.gif); }
#SideTabs li#sidetab_sitetree { background-image: url(../images/sidetabs/sitemap.gif); }
#SideTabs li#sidetab_tasklist { background-image: url(../images/sidetabs/tasklist.gif); }
#SideTabs li#sidetab_waitingon { background-image: url(../images/sidetabs/waitingon.gif); }
#SideTabs li#sidetab_comments { background-image: url(../images/sidetabs/comments.gif); }
#SideTabs li#sidetab_reports { background-image: url(../images/sidetabs/reports.gif); }
#SideTabs li#sidetab_search { background-image: url(../images/sidetabs/search.gif); }
#SideTabs li#sidetab_advertisements { background-image: url(../images/sidetabs/advertisements.gif); }
#SideTabs li#sidetab_versions.selected { background-image: url(../images/sidetabs/versions_over.gif); }
#SideTabs li#sidetab_sitetree.selected { background-image: url(../images/sidetabs/sitemap_over.gif); }
#SideTabs li#sidetab_tasklist.selected { background-image: url(../images/sidetabs/tasklist_over.gif); }
#SideTabs li#sidetab_waitingon.selected { background-image: url(../images/sidetabs/waitingon_over.gif); }
#SideTabs li#sidetab_comments.selected { background-image: url(../images/sidetabs/comments_over.gif); }
#SideTabs li#sidetab_reports.selected { background-image: url(../images/sidetabs/reports_over.gif); }
#SideTabs li#sidetab_search.selected { background-image: url(../images/sidetabs/search_over.gif); }
#SideTabs li#sidetab_advertisements.selected { background-image: url(../images/sidetabs/advertisements_over.gif); }
#left {
background-color: #EEE;
}
#treepanes {
background-color: #FFF;
border-left: 1px #ccc solid;
}
.listpane p {
margin: 3px 3px 7px 3px;
font-size: 10px;
}
.listpane ul {
margin: 0;
padding: 0;
}
.listpane ul li {
list-style-type: none;
padding: 5px 2px 5px 2px;
font-size: 10px;
border-bottom: 1px #CCC dotted;
background-color: #FFF;
}
.listpane ul li.odd {
background-color: #EEE;
}
.listpane ul li .extra {
font-style: italic;
color: #666;
}
.listpane div.unitBody {
overflow: auto;
width: 100%;
}
#CommentList {
padding: 0;
margin: 0;
}
#CommentList li {
list-style-type: none;
background-color: #EEE;
border: 1px #CCC solid;
padding: 3px;
margin: 7px;
}
#CommentList li .extra {
margin-top: 3px;
font-size: 80%;
font-style: italic;
}
#ReportSelector_holder {
background-color: #EEE;
border-bottom: 1px #CCC solid;
margin: 0;
padding: 3px;
}
#ReportSelector_holder select {
width: 100%;
}
#treepanes .pane_actions {
margin: 7px 4px;
}
#treepanes .pane_actions a {
margin: 4px;
padding: 3px;
background-color: #EEE;
border: 1px #CCC solid;
}
#treepanes .pane_actions a.current {
background-color: #CCC;
}
#treepanes table {
border-collapse: collapse;
width: 100%;
}
#treepanes table thead td {
background-color: #777;
color: white;
border-bottom: 1px #CCC solid;
}
#treepanes table tbody tr {
cursor: pointer;
}
#treepanes table tbody tr.odd td {
background-color: #EEE;
}
#treepanes table tbody tr.over td {
background-color: #FFFFBB;
}
#treepanes table tbody tr.current td {
background-color: #BBBBFF;
}
#treepanes #Versions tbody tr.internal {
color: #777;
}
#treepanes #Versions tbody tr.published {
color: black;
}
/**
* Change the styling of the root tree node
*/
ul.tree span.a.Root, ul.tree span.a.last.Root, ul.tree span.a.children.Root {
background-image: none;
}
ul.tree span.a.Root span.c, ul.tree span.a.last.Root span.c, ul.tree span.a.children.Root span.c {
margin: 0;
}
ul#sitetree.tree ul {
margin-left: 0;
}
ul#sitetree.tree ul ul {
margin-left: 16px;
}
ul.tree span.a.MailType, ul.tree span.a.last.MailType, ul.tree span.a.children.MailType {
background-image: none;
}
ul.tree span.a.MailType span.c, ul.tree span.a.last.MailType span.c, ul.tree span.a.children.MailType span.c {
margin: 0;
}
.contextMenu {
padding: 0;
margin: 0;
}
.contextMenu li {
list-style-type: none;
margin: 0;
padding: 0;
}
.contextMenu li a {
display: block;
font-family: Tahoma, Verdana, Arial, Helvetica;
font-size: 11px;
padding: 5px 10px 5px 5px;
color: black;
text-decoration: none;
}
.contextMenu li a:hover {
background-color: #B6BDD2;
text-decoration: none;
}

765
css/cms_right.css Normal file
View File

@ -0,0 +1,765 @@
.right form {
margin: 1em;
position: relative;
/*top: 70px;*/
}
html>body .right from {
/*top: 50px;*/
}
iframe {
border : none;
}
.right form div.field {
clear: both;
margin-left: 10em;
font-size: 1.2em;
vertical-align: middle;
}
.right form p.field {
clear: both;
font-size: 1.2em;
margin: 0;
}
.right form div.field.nolabel {
margin-left: 0;
}
.right form label.left {
float: left;
width: 10em;
margin-left: -10em;
}
.right form input.maxlength {
width: auto;
}
.right form textarea {
width: 98%;
}
.right form textarea.htmleditor {
width: 90%;
display : none;
}
html>body .right form textarea.htmleditor {
width: 98%;
}
.right form span.readonly {
border: 1px #CCC dotted;
background-color: #F7F7F7;
display: block;
width: 98%;
padding: 3px;
margin:5px 0;
}
.right form .validation,
.right form .error,
.right form .required
{
border: 1px solid #f00;
background: #fcc;
padding: 0.5em;
width: 50%;
}
.right form .TableField .message {
width: auto;
}
.right form .creditCardfield input {
width: 4.415em;
}
.right form input.checkbox, .right form .optionset input, .right form .htmleditor select, .right form input.action {
width: auto;
}
.right form ul.optionset {
padding: 0;
}
.right form .optionset li {
list-style: none;
}
.right form h2 {
clear: both;
}
.right form .fieldgroup input, .right form .fieldgroup select {
width: auto;
}
/**
* TableField (and subclasses)
*/
table.TableField,
table.TableListField,
.TableListField table.data,
table.CMSList {
border-collapse: collapse;
border-spacing: 0;
border : 1px solid #aaaaaa;
width : 100%;
}
/* HACK Preventing IE6 from showing double borders */
body>div table.TableField,
body>div table.TableListField,
body>div .TableListField table.data,
body>div table.CMSList {
border-collapse: separate;
}
table.TableField td,
table.TableListField td,
.TableListField table.data td,
table.CMSList td {
border-style:none;
}
table.TableField th,
table.TableListField th,
.TableListField table.data th,
table.CMSList th {
whitespace: no-wrap;
}
table.TableField thead th,
.TableListField table.data thead th,
table.CMSList thead th {
background-image : url(../../cms/images/tables/thead.png);
background-repeat : repeat-x;
background-position : left bottom;
background-color : #ebeadb;
height : 24px;
border-right : 1px solid #aca899;
border-left : 1px solid #ffffff;
padding-left : 5px;
}
table.TableField thead th span,
.TableListField table.data thead th span {
display: block;
float: left;
}
table.TableField thead th a,
.TableListField table.data thead th a,
table.CMSList thead th a {
color: #000;
}
table.TableField thead th span.sortLink,
.TableListField table.data thead th span.sortLink,
table.CMSList thead th span.sortLink {
width: 16px;
height: 16px;
overflow: hidden;
}
table.TableField tfoot tr.summary td,
.TableListField table.data tfoot tr.summary td,
table.CMSList tfoot tr.summary td {
background-color : #ebeadb;
}
table.TableField tbody td,
table.TableField tfoot td,
.TableListField table.data tbody td,
.TableListField table.data tfoot td,
table.CMSList tbody td,
table.CMSList tfoot td {
border : 1px solid #f1efe2;
padding-left : 5px;
}
.TableField td input,
.TableListField td input {
width: 98%;
}
table.data tbody td input {
border:0 !important;
}
table.TableField tbody td.checkbox,
.TableListField table.data tbody td.checkbox,
table.CMSList tbody td.checkbox {
border : 1px solid #f1efe2;
padding-left : 5px;
background-image : url(../../cms/images/tables/checkbox.png);
background-repeat : repeat-x;
background-position : left bottom;
}
table.TableField tbody tr.over td,
.TableListField table.data tbody tr.over td,
table.CMSList tbody td.over td{
background-color: #FFC;
}
table.TableField tbody tr.current td,
.TableListField table.data tbody tr.current td,
table.CMSList tbody td.current td {
background-color: #316ac5;
color : white;
}
.TableListField table.data tfoot .addlink img {
vertical-align: middle;
margin: 3px 6px 3px 3px;
}
.right form .TableField span.readonly {
border: 0;
background: none;
padding: 0;
margin-bottom: 0;
}
.TableListField div.utility {
margin-top: 0.5em;
}
.TableListField div.utility a {
font-size: 9px;
border: 1px solid #aaa;
padding: 2px;
}
/**
* Selection Groups
*/
.SelectionGroup {
padding: 0px;
clear : both;
}
.SelectionGroup li {
list-style-type: none;
float : left;
clear : both;
}
.SelectionGroup li input {
width: 20px;
float : left;
}
.SelectionGroup li div.field {
display: none;
font-size: 1em;
}
.SelectionGroup li input, .right .SelectionGroup li label {
display: block;
}
.SelectionGroup li.selected div.field {
margin-left: 30px;
display: block;
margin-bottom: 1em;
}
.SelectionGroup li.selected label.selector {
font-weight: bold;
}
/**
* Composite Fields - raw concatenation of fields for programmatic purposes.
*/
.right form div.field.CompositeField {
margin-left: 7.5em;
}
.right form div.field.CompositeField div.field {
font-size: 1em;
}
.right form div.field.CompositeField {
clear: both;
}
.right form div.field.CompositeField label.left {
float: left;
width: 10em;
margin-left: -10em;
}
.right form div.column2 {
float: left;
width: 45%;
margin-right: 4%;
}
.right form div.multicolumn {
width: 100%;
float: left;
clear: left;
}
/**
* Checkbox set fields
*/
#right form .CheckboxSetField ul{
margin:0;
padding:0;
}
#right form .CheckboxSetField ul li{
list-style:none !important;
margin:0;
padding:0;
}
#right form .CheckboxSetField input{
width:auto;
}
.ajaxActions {
position: absolute;
top: 0;
right: 0;
padding: 0;
margin: 0;
text-align: right;
background-image:url(../images/textures/ToolBar.png);
height : 21px;
border-left : 1px solid #808080;
border-bottom : 1px solid #808080;
}
.ajaxActions input {
padding: 0;
border-style: none;
background-color:transparent;
cursor: pointer;
float : left;
height : 22px;
background-image:url(../images/textures/seperator.png);
background-repeat:no-repeat;
background-position:right;
padding-left: 10px;
padding-right : 10px;
margin : 0px;
color : #333333;
}
.right form div.tab {
clear: left;
overflow: auto;
border: 1px #AAA solid;
border-top: none;
position: relative;
top: -3px;
margin: 0;
padding: 10px;
/*width: 98%;*/
}
.right form div.tabset {
border: 1px solid #fff; /* Hack for FF1.5/Win Float-Bug */
clear: left;
margin: 0;
/*width: 100%;*/
}
/**
* Bottom-right
*/
#rightbottom {
border : none;
margin-top : 4px;
margin-left : 1px;
border : 1px solid #808080;
}
#rightbottom form {
margin: 1em;
/*position: relative;
top: 50px;*/
}
#rightbottom form div {
margin-top : 3px;
}
#rightbottom form div.field {
clear: both;
margin-left: 10em;
font-size: 1.2em;
border : none;
}
#rightbottom form label.left {
float: left;
width: 10em;
margin-left: -10em;
}
#rightbottom form input, #rightbottom form select, #rightbottom form textarea {
width: 90%;
}
#rightbottom form input.checkbox, #rightbottom form .optionset input, #rightbottom form .htmleditor select, #rightbottom form input.action {
width: auto;
}
#rightbottom form ul.optionset {
padding: 0;
}
#rightbottom form .optionset li {
list-style: none;
}
#rightbottom form h2 {
clear: both;
}
#rightbottom form .fieldgroup input, #rightbottom form .fieldgroup select {
width: auto;
}
/**
* RHS Action Parameters boxes
*/
#right form.actionparams {
border-bottom: 1px #777 solid;
border-left: 1px #777 solid;
background-color: #CCC;
position: absolute;
top: 23px;
right: 0px;
width: 300px;
z-index: 200;
margin: 0;
padding: 10px;
}
#right form.actionparams div.field {
margin-left: 6em;
}
#right form.actionparams label.left {
width: 6em;
margin-left: -6em;
}
#right form.actionparams input, #right form.actionparams select, #right form.actionparams textarea {
width: 100%;
}
#right form.actionparams input.checkbox, #right form.actionparams .optionset input {
width: auto;
}
#right form.actionparams p.label {
margin: 10px 0 0 0;
}
#right form.actionparams p.actions {
margin: 10px 0 0 0;
text-align: right;
}
#right form.actionparams p.actions input {
width: auto;
}
#right form.actionparams .sendingText {
position: absolute;
left: 0;
top: 0;
width: 100%;
padding: 10% 0 0 0;
height: 60%;
background-color: #CCC;
font-size: 25px;
font-weight: bold;
color: white;
text-align: center;
}
#right form.actionparams div.TreeDropdownField {
width: 230px;
}
#right form.actionparams div.TreeDropdownField span.items {
width: 210px;
}
#right form.actionparams div.TreeDropdownField div.tree_holder {
width: 214px;
}
div.TreeDropdownField {
width: 35em;
padding: 0;
}
html>body div.TreeDropdownField {
position:relative;
}
div.TreeDropdownField span.items {
display: block;
height: 100%;
border: 1px #7f9db9 solid;
cursor: pointer;
width: 33em;
float: left;
padding-top: 2px;
padding-bottom: 2px;
background-color: white;
}
div.TreeDropdownField div.tree_holder {
clear: left;
cursor: default;
border: 1px black solid;
margin: 0;
height: 200px;
overflow: auto;
background-color: white;
/**
* HACK IE6, see http://www.hedgerwow.com/360/bugs/css-select-free.html
*/
position:absolute;
z-index:10;
width:33em;/*must have for any value*/;
}
html>body div.TreeDropdownField div.tree_holder {
top: 20px;
left: 0px;
z-index: 1000;
}
/**
* HACK IE6, see http://www.hedgerwow.com/360/bugs/css-select-free.html
*/
div.TreeDropdownField div.tree_holder iframe {
display:none;/* IE5*/
display/**/:block;/* IE5*/
position:absolute;
top:0;
left:0;
z-index:-1;
filter:mask();
width:31em;/*must have for any big value*/
height:200px/*must have for any big value*/;
}
div.TreeDropdownField a.editLink {
border-width: 1px 1px 1px 0;
background: url(../../sapphire/images/TreeDropdownField_button.gif) left top no-repeat;
width: 19px;
height: 21px;
margin: 0;
padding: 0;
float: left;
clear: right;
z-index: 0;
overflow: hidden;
}
.autocomplete {
background-color: #EEEEEE;
margin : 0px;
padding : 0px;
}
.autocomplete ul {
border: 1px #AAA solid;
margin : 0px;
padding : 0px;
}
.autocomplete li {
list-style-type: none;
cursor: pointer;
margin: 0px;
font-size : 12px;
padding : 3px;
white-space : nowrap;
}
.autocomplete .data {
display: none;
}
.autocomplete .informal {
font-style: italic;
}
/*
* Status
*/
#statusMessage {
position: absolute;
z-index: 500;
bottom: 15px;
right: 30px;
text-align: left;
padding: 15px 15px 15px 40px;
font-size: 16px;
font-weight: bold;
border: 1px solid #cc9;
color: #660;
background-color: #F9F9E3;
}
#statusMessage.good {
border-color: #9c9;
color: #060;
background: url(../images/alert-good.gif) #E2F9E3 7px no-repeat;
}
#statusMessage.bad {
border-color: #c99;
color: #fff;
background: url(../images/alert-bad.gif) #c00 7px no-repeat;
max-height: 600px;
overflow: auto;
}
.pageStatusMessage {
position: absolute;
top: 0;
right: 0;
background-color: #F9F9E3;
font-weight: bold;
font-size: 1.2em;
padding: 5px;
border: 1px solid #cc9;
}
#right ins * {
background-color: green;
}
#right del *{
background-color: red;
}
div#ImportFile iframe {
width: 80%;
height: auto;
border: none;
}
table.ReportField {
position: relative;
border: solid 1px #82C0FF;
border-collapse: collapse;
}
table.ReportField thead th {
background-color: #82C0FF;
}
table.ReportField td, table.ReportField {
border: solid 1px #82C0FF;
}
table.ReportField tr {
border-bottom: solid 1px #82C0FF;
}
div.ProgressBar {
text-align: center;
display: none;
}
div.ProgressBar p.text {
text-align: center;
margin-left: auto;
margin-right: auto;
}
div.ProgressBar div.BarHolder {
width: 100%;
border: solid 1px #777;
background-color: #FFF;
padding: 2px;
text-align: left;
}
div.ProgressBar div.BarHolder div.Bar {
width: 0%;
background-color: #0074C6;
}
div.MemberListFilter,
div.MemberFilter {
/*float: right;*/
width: 40em;
margin: 5px 0;
padding: 5px;
background-color : #ebeadb;
border: 1px solid #aca899;
}
.right form div.MemberListFilter label,
.right form div.MemberFilter label {
display: block;
width: 10em;
float: left;
margin-left: 0em;
}
div.MemberListFilter div.field,
div.MemberFilter div.field {
margin-left: 0em;
}
div.MemberListFilter input,
div.MemberListFilter select,
div.MemberFilter input,
div.MemberFilter select {
width: auto;
}
#right iframe.AWStatsReport {
width: 98%;
height: 85%;
}
#sitetree a.contents {
font-style: italic;
}
/**
* SecurityAdmin
*/
#Form_EditForm_Permissions .Arg,
#Form_EditForm_Permissions .Enabled {
width: 6em;
}
#Form_EditForm_Permissions .Arg input {
text-align: right;
}

18
css/dialog.css Normal file
View File

@ -0,0 +1,18 @@
p.message {
/*background: url(../cms/images/dialogs/alert.gif) repeat top left;*/
padding-left: 60px;
}
img {
float: left;
margin-left: -60px;
}
span {
clear: right;
}
body {
font-family: Arial, sans-serif;
font-size: 1em;
}

433
css/layout.css Normal file
View File

@ -0,0 +1,433 @@
/*
Tenative color scheme:
#0074C6 #95B2C6 #639DC6 #162F42 #2C5F84
4A7695 819BAD
*/
html {
margin: 0;
height: 100%;
overflow:hidden;
}
body {
height: 100%;
margin: 0;
font-size: 62.5%;
background-color: #d4d0c8;
}
#top, #left, #bottom, div.title, div.mceToolbarExternal, ul.tabstrip, div.field label, #separator {
/*-moz-user-select: none;*/ /*need to explicitly allow input box selection becuase of this */
}
form fieldset {
margin: 0;
padding: 0;
border-style: none;
}
form#Form_EditForm fieldset {
height: 100%;
}
body.stillLoading select {
display: none;
}
/** 3-PANEL LAYOUT **/
#top {
font-size: 1.4em;
height: 51px;
padding: 3px;
background: url(../images/mainmenu/top-bg.gif) top left repeat-x;
color: #FFF;
border-bottom: 3px solid #d4d0c8;
overflow: hidden;
}
#left {
position: absolute;
top: 51px;
left: 3px;
width: 205px;
height: 92%;
z-index: 50;
border : 1px #808080 solid;
background-color:#FFFFFF;
}
#right, #rightbottom {
position: absolute;
left: 208px;
top: 51px;
height: 92%;
width: 600px;
overflow: auto;
z-index: 50;
border: 1px #808080 solid;
background-color: white;
margin-right : 3px;
}
#rightbottom {
height: 30%;
}
#separator {
position: absolute;
top: 51px;
left: 205px;
height: 92%;
width: 3px;
cursor: e-resize;
border-left : 1px solid #ffffff;
}
/**
* Hidden left-hand panel
*/
#left.hidden form, #left.hidden .title, #left.hidden #TreeActions {
display: none;
}
#left.hidden #SideTabs {
margin-top: 47px;
}
#left.hidden {
width: 18px;
display: block;
}
#separator.hidden {
display: none;
}
#left div.title, #right div.title, #rightbottom div.title{
border-top: 1px #77BBEE solid;
background-image:url(../images/textures/obar.gif);
height : 22px !important;
background-color : #0075C9;
background-repeat : repeat-x !important;
background-position: 0px 0px;
}
#rightbottom div.light {
background-image: url(../images/textures/obar-light.png) !important;
}
#left div.title div, #right div.title div, #rightbottom div.title div{
font-size : 14px;
font-weight : bold;
color: white;
padding: 2px 0 0 4px;
background-position: 2px 2px;
background-repeat:no-repeat;
padding-left : 20px;
border-top: 1px #77BBEE solid;
}
#left h2 {
margin: 0;
background-image:url(../images/textures/obar-18.gif);
height: 18px;
color: white;
font-size: 12px;
padding-left: 3px;
}
/** TOP PANEL **/
#top #MainMenu {
margin: 0;
padding: 0;
}
#top #MainMenu li {
margin: 8px 8px 0 8px;
padding: 6px 8px 0 28px;
list-style-type: none;
float: left;
height: 32px;
background-repeat: no-repeat;
cursor: pointer;
}
html>body #top #MainMenu li {
height: 28px; /* 32 - 6 from the padding */
}
#top #MainMenu a {
color: white;
}
#top #MainMenu .current {
background-color: #d4d0c8;
background-position: 5px 5px;
position: relative;
top : -7px;
height : 31px;
padding-top : 12px;
padding-left : 30px;
-moz-border-radius-topright : 8px;
-moz-border-radius-topleft : 8px;
border : 1px solid #65686e;
border-bottom : none;
}
#top #MainMenu .current A:link,#top #MainMenu .current A:visited {
color : black;
}
#top #MainMenu #Menu-content { background-image: url(../images/mainmenu/content.gif); }
#top #MainMenu #Menu-security { background-image: url(../images/mainmenu/members.gif); }
#top #MainMenu #Menu-report { background-image: url(../images/mainmenu/reports.gif); }
#top #MainMenu #Menu-newsletter { background-image: url(../images/mainmenu/emails.gif); }
#top #MainMenu #Menu-files { background-image: url(../images/mainmenu/files.gif); }
#top #MainMenu #Menu-help { background-image: url(../images/mainmenu/help.gif);
float: right;
/*padding: 6px 28px 0 8px;
background-position: 100% 0;*/
margin-right: 60px;
}
#top #Logo {
float: right;
margin: 0;
padding: 16px 60px 16px 0;
height: 32px;
font-size: 16px;
background: url(../images/mainmenu/logo.gif) right top no-repeat;
}
html>body #top #Logo {
padding-bottom: 0;
}
#top #Logo a {
color: white;
}
#bottom {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 3px solid #d4d0c8;
background-color:#81858d;
height: 18px;
overflow:hidden;
background-image:url(../images/textures/bottom.png);
}
#bottom .holder {
text-align: center;
padding-top : 3px;
padding-left : 3px;
padding-right : 6px;
font-size: 10px;
color: white;
border-top: 1px solid #555555;
}
#bottom #logInStatus {
float: right;
}
#bottom #switchView {
float: left;
}
#bottom .bottomTabs a {
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;
-moz-border-radius-bottomright : 4px;
-moz-border-radius-bottomleft : 4px;
}
#bottom .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;
border : none;
background-color: transparent;
padding-right: 2px;
padding-left: 2px;
padding-top : 2px;
color:#FFFFFF;
}
#bottom .bottomTabs a.current {
background-color : #d4d0c8;
padding-top : 1px;
top : -5px;
height : 15px;
font-weight:bold;
font-size : 11px;
border : 1px solid #555555;
}
/** LEFT PANEL **/
#sitetree_holder {
height: 80%;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
}
#sitetree {
width: 500px; /* IE's chocking right now */
}
html> #sitetree {
width: auto;
}
div.spacer, li.spacer {
list-style-type: none;
float: none;
clear: both;
background-color: transparent;
border-style: none;
padding: 0;
margin: -1px 0 0 0;
height: 1px;
font-size: 1px;
width: auto;
}
/** RIGHT PANEL **/
.mceToolbarExternal {
background-color: #EEE;
border-bottom: 1px #CCC solid;
/*position: absolute;
top: 23px;
left: 0;*/
display: block;
}
#right form#Form_EditorToolbarLinkForm,
#right form#Form_EditorToolbarImageForm,
#right form#Form_EditorToolbarFlashForm {
background-color: #EEE;
border-bottom: 1px #CCC solid;
display: none;
margin: 1px 0 0 0;
padding: 5px;
height: 115px;
/*
* HACK IE (all versions): container needs to be higher stacking order
* than any DOM-elemnt under it.
* @see http://www.aplus.co.yu/lab/z-pos/
*/
z-index: 1001;
}
#right form#Form_EditorToolbarImageForm {
height: 160px;
}
#right form#Form_EditorToolbarFlashForm {
height: 130px;
}
#right form#Form_EditorToolbarLinkForm ul.optionset {
height: 1em;
margin: 0;
}
#right form#Form_EditorToolbarLinkForm ul.optionset li {
float: left;
}
.thumbnailstrip {
height: 58px;
overflow: auto;
white-space: nowrap;
width: 100%;
float: left;
border : 1px solid #AAAAAA;
}
.thumbnailstrip ul {
margin: 0;
padding: 0;
list-style: none;
}
.thumbnailstrip li {
float: left;
margin-right: 4px;
}
.indicator.inline {
display: inline;
margin-left: 5px;
vertical-align: middle;
}
p.Actions {
vertical-align: middle;
}
.indicator.block {
display: inline;
}
/* CMS specific icons for the tree */
ul.tree li.Root span.Root span.c a {
background-image : url(../../cms/images/treeicons/root.png) !important;
}

49
css/typography.css Normal file
View File

@ -0,0 +1,49 @@
body {
font-size: 62.5%;
}
* {
font-family: Arial;
font-size: 1em;
}
p, ul, td, th {
font-size: 1.2em;
}
/* overrides (to avoid larger font-sizing) */
ul ul,
th p,
td p,
div ul.optionset {
font-size: 1em;
}
ul ul {
font-size: 1em;
}
h1 {
font-size: 2em;
color: #0074C6;
}
h2 {
font-size: 1.8em;
color: #0074C6;
}
a {
color: #162F42;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a img {
border-style: none;
}
#Header a, #ProductGroups a, #Products a {
color: white;
}
#Header h1 {
color: white;
}

BIN
images/add.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1004 B

BIN
images/alert-bad.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
images/alert-good.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
images/block.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
images/bullet_arrow_down.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

BIN
images/bullet_arrow_up.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

BIN
images/button-bg.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

BIN
images/button-left.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

BIN
images/button-right.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

BIN
images/check.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
images/delete-small.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

BIN
images/delete.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

BIN
images/dialogs/alert.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
images/dialogs/alert.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
images/down.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

BIN
images/edit.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
images/loading.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
images/locked.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

BIN
images/mainmenu/content.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

BIN
images/mainmenu/content.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
images/mainmenu/emails.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

BIN
images/mainmenu/files.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 B

BIN
images/mainmenu/help.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

BIN
images/mainmenu/help.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
images/mainmenu/logo.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

BIN
images/mainmenu/logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
images/mainmenu/members.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

BIN
images/mainmenu/members.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
images/mainmenu/mgmt.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
images/mainmenu/reports.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

BIN
images/mainmenu/top-bg.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

BIN
images/network-save-bw.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

BIN
images/network-save.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
images/network-saveOLD.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

BIN
images/pagination/record-last.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

BIN
images/pagination/record-next.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

BIN
images/pagination/record-prev.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

BIN
images/panels/EditPage.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

BIN
images/panels/MySite.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/right.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

BIN
images/show.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

BIN
images/sidetabs/comments.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

BIN
images/sidetabs/comments_over.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

BIN
images/sidetabs/reports.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

BIN
images/sidetabs/reports_over.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

BIN
images/sidetabs/search.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

BIN
images/sidetabs/search_over.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

BIN
images/sidetabs/sitemap.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

BIN
images/sidetabs/sitemap_over.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

BIN
images/sidetabs/tasklist.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

BIN
images/sidetabs/tasklist_over.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

BIN
images/sidetabs/template over.psd Executable file

Binary file not shown.

BIN
images/sidetabs/versions.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

BIN
images/sidetabs/versions_over.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

BIN
images/sidetabs/waitingon.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

BIN
images/tables/checkbox.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

BIN
images/tables/thead.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

BIN
images/textures/ToolBar.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

BIN
images/textures/bottom.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

BIN
images/textures/obar-18.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 B

BIN
images/textures/obar-light.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

BIN
images/textures/obar.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

BIN
images/textures/seperator.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

BIN
images/tickbox-canttick.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

BIN
images/tickbox-greyticked.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

BIN
images/tickbox-ticked.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

BIN
images/tickbox-unticked.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

BIN
images/treeicons/book-file.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

BIN
images/treeicons/draft-file.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
images/treeicons/draft-folder.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

BIN
images/treeicons/element-file.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

BIN
images/treeicons/folder-file.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

BIN
images/treeicons/home-file.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

BIN
images/treeicons/multi-user.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

BIN
images/treeicons/multi-user.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

BIN
images/treeicons/reports-file.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

BIN
images/treeicons/root.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

BIN
images/treeicons/sent-file.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

BIN
images/treeicons/sent-folder.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

BIN
images/treeicons/task-file.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

BIN
images/treeicons/user-file.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

BIN
images/unlocked.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

BIN
images/unlockedled.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

BIN
images/workflow/note_edit.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
images/workflow/note_new.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
images/workflow/note_view.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
images/workflow/rubberstamp.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

280
javascript/AssetAdmin.js Executable file
View File

@ -0,0 +1,280 @@
/**
* Configuration for the left hand tree
*/
if(typeof SiteTreeHandlers == 'undefined') SiteTreeHandlers = {};
SiteTreeHandlers.parentChanged_url = 'admin/assets/ajaxupdateparent';
SiteTreeHandlers.orderChanged_url = 'admin/assets/ajaxupdatesort';
SiteTreeHandlers.loadPage_url = 'admin/assets/getitem';
SiteTreeHandlers.loadTree_url = 'admin/assets/getsubtree';
SiteTreeHandlers.showRecord_url = 'admin/assets/show/';
var _HANDLER_FORMS = {
addpage : 'addpage_options',
deletepage : 'deletepage_options',
sortitems : 'sortitems_options'
};
/**
* Top-right actions
*/
function action_upload_right(e) {
if(frames['AssetAdmin_upload'].document && frames['AssetAdmin_upload'].document.getElementById('Form_UploadForm')) {
// make sure at least one file is selected for upload
var values = "";
var inputs = $A(frames['AssetAdmin_upload'].document.getElementsByTagName("input"));
inputs.each(function(input) {
if(input.type == "file") values += input.value;
}.bind(this));
if(values.length == 0) {
alert("Please select at least one file for uploading");
openTab("Root_Upload");
} else {
frames['AssetAdmin_upload'].document.getElementById('Form_UploadForm').submit();
}
}
Event.stop(e);
return false;
}
function action_deletemarked_right() {
$('action_deletemarked_options').onComplete = function() {}
if(confirm("Do you really want to delete the marked files?")) {
$('action_deletemarked_options').send();
}
}
function action_movemarked_right() {
$('action_movemarked_options').toggle();
}
MarkingPropertiesForm = Class.extend('ActionPropertiesForm');
MarkingPropertiesForm.applyTo('#action_movemarked_options', "Please select some files to move!");
MarkingPropertiesForm.applyTo('#action_deletemarked_options', "Please select some files to delete!");
MarkingPropertiesForm.prototype = {
initialize: function(noneCheckedError) {
this.noneCheckedError = noneCheckedError;
},
send: function() {
var i, list = "", checkboxes = $('Form_EditForm').elements['Files[]'];
if(!checkboxes) checkboxes = [];
if(!checkboxes.length) checkboxes = [ checkboxes ];
for(i=0;i<checkboxes.length;i++) {
if(checkboxes[i].checked) list += (list?',':'') + checkboxes[i].value;
}
if(this.elements.DestFolderID && !this.elements.DestFolderID.value) {
alert("Please select a folder to move files to!");
return false;
} else if(list == "") {
alert(this.noneCheckedError);
return false;
} else {
this.elements.FileIDs.value = list;
return this.ActionPropertiesForm.send();
}
}
}
/**
* Add File Action
*/
addfolder = Class.create();
addfolder.applyTo('#addpage');
addfolder.prototype = {
initialize: function () {
Observable.applyTo($(this.id + '_options'));
this.getElementsByTagName('a')[0].onclick = returnFalse;
$(this.id + '_options').onsubmit = this.form_submit;
},
onclick : function() {
statusMessage('Creating new folder...');
this.form_submit();
/*
if(treeactions.toggleSelection(this)) {
var selectedNode = $('sitetree').firstSelected();
if(selectedNode) {
while(selectedNode.parentTreeNode && !selectedNode.hints.defaultChild) {
$('sitetree').changeCurrentTo(selectedNode.parentTreeNode);
selectedNode = selectedNode.parentTreeNode;
}
}
}
*/
return false;
},
form_submit : function() {
var st = $('sitetree');
$('addpage_options').elements.ParentID.value = st.getIdxOf(st.firstSelected());
Ajax.SubmitForm('addpage_options', null, {
onSuccess : Ajax.Evaluator,
onFailure : this.showAddPageError
});
return false;
},
showAddPageError: function(response) {
errorMessage('Error adding folder', response);
}
}
/**
* Delete folder action
*/
deletefolder = {
button_onclick : function() {
if(treeactions.toggleSelection(this)) {
deletefolder.o1 = $('sitetree').observeMethod('SelectionChanged', deletefolder.treeSelectionChanged);
deletefolder.o2 = $('deletepage_options').observeMethod('Close', deletefolder.popupClosed);
addClass($('sitetree'),'multiselect');
deletefolder.selectedNodes = { };
var sel = $('sitetree').firstSelected()
if(sel) {
var selIdx = $('sitetree').getIdxOf(sel);
deletefolder.selectedNodes[selIdx] = true;
sel.removeNodeClass('current');
sel.addNodeClass('selected');
}
}
return false;
},
treeSelectionChanged : function(selectedNode) {
var idx = $('sitetree').getIdxOf(selectedNode);
if(selectedNode.selected) {
selectedNode.removeNodeClass('selected');
selectedNode.selected = false;
deletefolder.selectedNodes[idx] = false;
} else {
selectedNode.addNodeClass('selected');
selectedNode.selected = true;
deletefolder.selectedNodes[idx] = true;
}
return false;
},
popupClosed : function() {
removeClass($('sitetree'),'multiselect');
$('sitetree').stopObserving(deletefolder.o1);
$('deletepage_options').stopObserving(deletefolder.o2);
for(var idx in deletefolder.selectedNodes) {
if(deletefolder.selectedNodes[idx]) {
node = $('sitetree').getTreeNodeByIdx(idx);
if(node) {
node.removeNodeClass('selected');
node.selected = false;
}
}
}
},
form_submit : function() {
var csvIDs = "";
for(var idx in deletefolder.selectedNodes) {
var selectedNode = $('sitetree').getTreeNodeByIdx(idx);
var link = selectedNode.getElementsByTagName('a')[0];
if(deletefolder.selectedNodes[idx] && ( !Element.hasClassName( link, 'contents' ) || confirm( "'" + link.firstChild.nodeValue + "' contains files. Would you like to delete the files and folder?" ) ) )
csvIDs += (csvIDs ? "," : "") + idx;
}
if(csvIDs) {
$('deletepage_options').elements.csvIDs.value = csvIDs;
statusMessage('deleting pages');
Ajax.SubmitForm('deletepage_options', null, {
onSuccess : deletefolder.submit_success,
onFailure : function(response) {
errorMessage('Error deleting pages', response);
}
});
$('deletepage').getElementsByTagName('a')[0].onclick();
} else {
alert("Please select at least 1 page.");
}
return false;
},
submit_success: function(response) {
Ajax.Evaluator(response);
treeactions.closeSelection($('deletepage'));
}
}
Behaviour.register({
'#Form_EditForm_Files': {
removeFile : function(fileID) {
var record;
if(record = $('record-' + fileID)) {
record.parentNode.removeChild(record);
}
}
},
'#Form_EditForm_Files a.deletelink' : {
onclick : function(event) {
ajaxLink(this.href);
Event.stop(event);
return false;
}
},
'#Form_EditForm' : {
changeDetection_fieldsToIgnore : {
'Files[]' : true
}
}
});
/**
* We don't want hitting the enter key in the name field
* to submit the form.
*/
Behaviour.register({
'#Form_EditForm_Name' : {
onkeypress : function(event) {
event = (event) ? event : window.event;
var kc = event.keyCode ? event.keyCode : event.charCode;
if(kc == 13) {
return false;
}
}
}
});
/**
* Initialisation function to set everything up
*/
appendLoader(function () {
// Set up delete page
Observable.applyTo($('deletepage_options'));
if($('deletepage')) {
$('deletepage').onclick = deletefolder.button_onclick;
$('deletepage').getElementsByTagName('a')[0].onclick = function() { return false; };
$('deletepage_options').onsubmit = deletefolder.form_submit;
}
});

15
javascript/BulkLoaderAdmin.js Executable file
View File

@ -0,0 +1,15 @@
/**
* Configuration for the left hand tree
*/
if(typeof SiteTreeHandlers == 'undefined') SiteTreeHandlers = {};
SiteTreeHandlers.loadPage_url = 'admin/bulkload/getitem';
SiteTreeHandlers.showRecord_url = 'admin/bulkload/show/';;
Behaviour.register({
'#Form_EditForm' : {
getPageFromServer: function (className) {
$('BulkLoaderIframe').src = 'admin/bulkload/iframe/' + className;
}
}
});

0
javascript/CMSMain.js Executable file
View File

270
javascript/CMSMain_left.js Executable file
View File

@ -0,0 +1,270 @@
if(typeof SiteTreeHandlers == 'undefined') SiteTreeHandlers = {};
SiteTreeHandlers.parentChanged_url = 'admin/ajaxupdateparent';
SiteTreeHandlers.orderChanged_url = 'admin/ajaxupdatesort';
SiteTreeHandlers.loadPage_url = 'admin/getitem';
SiteTreeHandlers.loadTree_url = 'admin/getsubtree';
_NEW_PAGES = new Array();
/**
* Add page action
*/
addpage = Class.create();
addpage.applyTo('#addpage');
addpage.prototype = {
initialize: function () {
Observable.applyTo($(_HANDLER_FORMS[this.id]));
this.getElementsByTagName('a')[0].onclick = returnFalse;
$(_HANDLER_FORMS[this.id]).onsubmit = this.form_submit;
},
onclick : function() {
if(treeactions.toggleSelection(this)) {
var selectedNode = $('sitetree').firstSelected();
if(selectedNode) {
while(selectedNode.parentTreeNode && !selectedNode.hints.defaultChild) {
$('sitetree').changeCurrentTo(selectedNode.parentTreeNode);
selectedNode = selectedNode.parentTreeNode;
}
if( selectedNode.hints && selectedNode.hints.defaultChild )
$(_HANDLER_FORMS.addpage).elements.PageType.value = selectedNode.hints.defaultChild;
}
this.o1 = $('sitetree').observeMethod('SelectionChanged', this.treeSelectionChanged.bind(this));
this.o2 = $(_HANDLER_FORMS[this.id]).observeMethod('Close', this.popupClosed.bind(this));
$(_HANDLER_FORMS[this.id]).elements.PageType.onchange = this.typeDropdown_change;
}
return false;
},
treeSelectionChanged : function(selectedNode) {
$(_HANDLER_FORMS.addpage).elements.PageType.value = selectedNode.hints.defaultChild;
},
popupClosed : function() {
$('sitetree').stopObserving(this.o1);
$(_HANDLER_FORMS.addpage).stopObserving(this.o2);
},
typeDropdown_change : function() {
// Don't do anything if we're already on an appropriate node
var sel = $('sitetree').firstSelected();
if(sel && sel.hints && sel.hints.allowedChildren) {
var allowed = sel.hints.allowedChildren;
for(i=0;i<allowed.length;i++) if(allowed[i] == this.value) return;
}
if( typeof siteTreeHints == 'undefined' )
return;
// Otherwise move to the default parent for that.
if( siteTreeHints && siteTreeHints[this.value] ) {
var newNode = $('sitetree').getTreeNodeByIdx(siteTreeHints[this.value].defaultParent);
if(newNode) $('sitetree').changeCurrentTo(newNode);
}
},
form_submit : function() {
var st = $('sitetree');
var parentID = st.getIdxOf(st.firstSelected());
if(parentID && parentID.substr(0,3) == 'new') {
alert("You have to save a page before adding children underneath it");
} else if( Element.hasClassName( st.firstSelected(), "nochildren" ) ) {
alert("You can't add children to the selected node" );
} else {
$(_HANDLER_FORMS.addpage).elements.ParentID.value = parentID ? parentID : 0;
if( !_NEW_PAGES[parentID] )
_NEW_PAGES[parentID] = 1;
var suffix = _NEW_PAGES[parentID]++;
Ajax.SubmitForm(_HANDLER_FORMS.addpage, "action_addpage", {
onSuccess : Ajax.Evaluator,
onFailure : this.showAddPageError,
extraData: '&Suffix=' + suffix
});
}
return false;
},
showAddPageError: function(response) {
errorMessage('Error adding page', response);
}
}
/**
* Delete page action
*/
deletepage = {
button_onclick : function() {
if(treeactions.toggleSelection(this)) {
deletepage.o1 = $('sitetree').observeMethod('SelectionChanged', deletepage.treeSelectionChanged);
deletepage.o2 = $(_HANDLER_FORMS.deletepage).observeMethod('Close', deletepage.popupClosed);
addClass($('sitetree'),'multiselect');
deletepage.selectedNodes = { };
var sel = $('sitetree').firstSelected();
if(sel && sel.className.indexOf('nodelete') == -1) {
var selIdx = $('sitetree').getIdxOf(sel);
deletepage.selectedNodes[selIdx] = true;
sel.removeNodeClass('current');
sel.addNodeClass('selected');
}
}
return false;
},
treeSelectionChanged : function(selectedNode) {
var idx = $('sitetree').getIdxOf(selectedNode);
if(selectedNode.className.indexOf('nodelete') == -1) {
if(selectedNode.selected) {
selectedNode.removeNodeClass('selected');
selectedNode.selected = false;
deletepage.selectedNodes[idx] = false;
} else {
selectedNode.addNodeClass('selected');
selectedNode.selected = true;
deletepage.selectedNodes[idx] = true;
}
}
return false;
},
popupClosed : function() {
removeClass($('sitetree'),'multiselect');
$('sitetree').stopObserving(deletepage.o1);
$(_HANDLER_FORMS.deletepage).stopObserving(deletepage.o2);
for(var idx in deletepage.selectedNodes) {
if(deletepage.selectedNodes[idx]) {
node = $('sitetree').getTreeNodeByIdx(idx);
if(node) {
node.removeNodeClass('selected');
node.selected = false;
}
}
}
},
form_submit : function() {
var csvIDs = "", count = 0;
var st = $('sitetree');
var newNodes = new Array();
for(var idx in deletepage.selectedNodes) {
if(deletepage.selectedNodes[idx]) {
// delete new nodes
if( idx.match(/^new-[a-z0-9A-Z\-]+$/) ) {
newNodes.push( idx );
} else {
var i, item, childrenToDelete = st.getTreeNodeByIdx(idx).getElementsByTagName('li');
for(i=0;item=childrenToDelete[i];i++) {
csvIDs += (csvIDs ? "," : "") + st.getIdxOf(childrenToDelete[i]);
count++;
}
csvIDs += (csvIDs ? "," : "") + idx;
count++;
}
}
}
if(csvIDs || newNodes.length > 0) {
count += newNodes.length;
if(confirm("Do you really want to delete the " + count + " marked pages?")) {
$(_HANDLER_FORMS.deletepage).elements.csvIDs.value = csvIDs;
statusMessage('deleting pages');
for( var idx = 0; idx < newNodes.length; idx++ ) {
var newNode = $('sitetree').getTreeNodeByIdx( newNodes[idx] );
if( newNode.parentTreeNode )
newNode.parentTreeNode.removeTreeNode( newNode );
else
alert( newNode.id + ' has no parent node');
$('Form_EditForm').reloadIfSetTo(idx);
}
newNodes = new Array();
Ajax.SubmitForm(_HANDLER_FORMS.deletepage, null, {
onSuccess : deletepage.submit_success,
onFailure : function(response) {
errorMessage('Error deleting pages', response);
}
});
$('deletepage').getElementsByTagName('a')[0].onclick();
}
} else {
alert("Please select at least 1 page.");
}
return false;
},
submit_success: function(response) {
deletepage.selectedNodes = {};
Ajax.Evaluator(response);
treeactions.closeSelection($('deletepage'));
}
}
/**
* Initialisation function to set everything up
*/
appendLoader(function () {
// Set up deleet page
if( !$('deletepage') )
return;
Observable.applyTo($(_HANDLER_FORMS.deletepage));
$('deletepage').onclick = deletepage.button_onclick;
$('deletepage').getElementsByTagName('a')[0].onclick = function() { return false; };
$(_HANDLER_FORMS.deletepage).onsubmit = deletepage.form_submit;
});
/**
* Tree context menu
*/
TreeContextMenu = {
'Edit this page' : function(treeNode) {
treeNode.selectTreeNode();
},
'Duplicate this page' : function(treeNode) {
new Ajax.Request(baseHref() + 'admin/duplicate/' + treeNode.getIdx() + '?ajax=1', {
method : 'get',
onSuccess : Ajax.Evaluator,
onFailure : function(response) {
errorMessage('Error: ', response);
}
});
},
'Sort sub-pages' : function(treeNode) {
var children = treeNode.treeNodeHolder().childTreeNodes();
var sortedChildren = children.sort(function(a, b) {
var titleA = a.aTag.innerHTML.replace(/<[^>]*>/g,'');
var titleB = b.aTag.innerHTML.replace(/<[^>]*>/g,'');
return titleA < titleB ? -1 : (titleA > titleB ? 1 : 0);
});
var i,child;
for(i=0;child=sortedChildren[i];i++) {
treeNode.appendTreeNode(child);
}
treeNode.onOrderChanged(sortedChildren);
}
};

123
javascript/CMSMain_right.js Executable file
View File

@ -0,0 +1,123 @@
function action_publish_right() {
$('Form_EditForm').notify('BeforeSave');
Ajax.SubmitForm('Form_EditForm', 'action_publish', {
onSuccess : function(response) {
Ajax.Evaluator(response);
},
onFailure : function(response) {
errorMessage('Error publishing content', response);
}
});
}
function action_revert_right() {
Ajax.SubmitForm('Form_EditForm', 'action_revert', {
onSuccess : Ajax.Evaluator,
onFailure : function(response) {
errorMessage('Error reverting to live content', response);
}
});
}
/*function action_submit_right() {
$('action_submit_options').configure('CanApprove', 'Awaiting editor approval');
$('action_submit_options').toggle();
}*/
function action_reject_right() {
$('action_submit_options').configure('CanReject', 'Content declined');
$('action_submit_options').toggle();
}
function action_submit_right() {
$('action_submit_options').configure('CanPublish', 'Awaiting final approval');
$('action_submit_options').toggle();
}
function action_rollback_right() {
var options = {
OK: function() {
var pageID = $('Form_EditForm').elements.ID.value;
Ajax.SubmitForm('Form_EditForm', 'action_rollback', {
onSuccess : function(response) {
$('Form_EditForm').getPageFromServer(pageID);
statusMessage(response.responseText,'good');
},
onFailure : function(response) {
errorMessage('Error rolling back content', response);
}
});
},
Cancel:function() {
}
}
doYouWantToRollback(options);
}
/**
* Email containing the link to the archived version of the page
*/
function action_email_right() {
window.open( 'mailto:?subject=' + $('Form_EditForm_ArchiveEmailSubject').value + '&body=' + $('Form_EditForm_ArchiveEmailMessage').value, 'archiveemail' );
}
function action_print_right() {
var printURL = $('Form_EditForm').action.replace(/\?.*$/,'') + '/printable/' + $('Form_EditForm').elements.ID.value;
if(printURL.substr(0,7) != 'http://') printURL = baseHref() + printURL;
window.open(printURL, 'printable');
}
Submit_ActionPropertiesForm = Class.extend('ActionPropertiesForm');
Submit_ActionPropertiesForm.applyTo('#action_submit_options');
Submit_ActionPropertiesForm.prototype = {
/**
* Define how this form is to be used
*/
configure : function(securityLevel, status) {
this.securityLevel = securityLevel;
this.elements.Status.value = status
this.elements.Status.parentNode.getElementsByTagName('span')[0].innerHTML= status;
}
}
function suggestStageSiteLink() {
var el = $('viewStageSite');
el.flasher = setInterval(flashColor.bind(el), 300);
setTimeout(stopFlashing.bind(el), 3000);
}
function flashColor() {
if(!this.style.color) this.style.color = '';
this.style.color = (this.style.color == '') ? '#00FF00' : '';
}
function stopFlashing() {
clearInterval(this.flasher);
}
Behaviour.register({
'a.cmsEditlink' : {
onclick : function() {
if(this.href.match(/admin\/show\/([0-9]+)($|#|\?)/)) {
$('Form_EditForm').getPageFromServer(RegExp.$1);
return false;
}
}
}
});
Behaviour.register({
'select#Form_EditForm_ClassName' : {
onchange: function() {
alert('The page type will be updated after the page is saved');
}
}
});
Behaviour.register({
'#Form_EditForm' : {
changeDetection_fieldsToIgnore : {
'restricted-chars[Form_EditForm_URLSegment]' : true,
'Sort' : true
}
}
});

13
javascript/CommentList.js Executable file
View File

@ -0,0 +1,13 @@
CommentList = Class.extend('SidePanel');
CommentList.prototype = {
destroy: function() {
if(this.SidePanel) this.SidePanel.destroy();
this.SidePanel = null;
},
onpagechanged : function() {
this.body.innerHTML = '<p>loading...</p>';
this.ajaxGetPanel(this.afterPanelLoaded);
}
}
CommentList.applyTo('#comments_holder');

115
javascript/ForumAdmin.js Executable file
View File

@ -0,0 +1,115 @@
var _GroupID;
Behaviour.register({
'#Form_EditForm_Type input':{
onclick:function(){
var requiredlogin = $('Form_EditForm_RequiredLogin');
var usersTab = $('Form_EditForm').getElementsByTagName('ul')[0].getElementsByTagName('li')[2];
if(this.value == 'consultation'){
if(requiredlogin.checked != 'checked')
requiredlogin.checked = 'checked';
usersTab.style.display = 'block';
if(_GroupID)
this.recoverGroupID();
Element.disable(requiredlogin);
}else{ // this.value == 'open'
usersTab.style.display = 'none';
this.treatGroupIDAs0();
Element.enable(requiredlogin);
}
},
treatGroupIDAs0:function(){
var groupIDDiv = $('GroupID');
var groupIDs = groupIDDiv.getElementsByTagName('option');
for(var i=0; i<groupIDs.length; i++){
if(groupIDs[i].selected == true){
groupIDs[i].selected = false;
break;
}
}
_GroupID = groupIDs[i].value;
groupIDs[0].selected = true;
},
recoverGroupID:function(){
var groupIDDiv = $('GroupID');
var groupIDs = groupIDDiv.getElementsByTagName('option');
groupIDs[0].selected = false;
for(var i=0; i<groupIDs.length; i++){
if(groupIDs[i].value == _GroupID){
groupIDs[i].selected = true;
break;
}
}
_GrouipID = null;
}
},
'#Root_Users #GroupID select':{
onchange:function() {
var source = this.getElementsByTagName('option');
for(var i=0; i<source.length; i++){
if(source[i].selected == true){
break;
}
}
var action=getAjaxURL('getMembersByGroup', source[i].value, 'ajax=1');
new Ajax.Updater(
{success: 'MemberList'},
action,
{
method: 'get',
onFailure: function(response) {errorMessage("Error getting data", response);},
onComplete: function() {Behaviour.apply($('MemberList'));}
}
);
return false;
}
}
});
function getAjaxURL(action, param, getvars) {
var base = document.getElementsByTagName('base')[0].href;
var url = window.location.href.substr(base.length);
if(url.match(/^([^?]+)(\?.*)/)){
url=RegExp.$1;
}
if(!url.match(/^([^\/]+\/)$/)){
url = url+"/";
}
if(getvars) getvars = "?" + getvars;
else getvars = "";
return base + url.replace(/^([^\/]+\/).*/, '$1' + action + '/' + param + getvars);
}
Element.disable = function(el){
el.disabled = true;
}
Element.enable = function(el){
el.disabled = false;
}
initialiseCMSIfAForumLoaded = function(){
if($('Form_EditForm_Type')) {
var types=($('Form_EditForm_Type').getElementsByTagName('input'));
for(var i=0; i<types.length; i++){
if(types[i].checked)
{
if(types[i].onclick) types[i].onclick();
}
}
}
}
Behaviour.addLoader(function(){
if($('Form_EditForm').observeMethod) {
$('Form_EditForm').observeMethod('PageLoaded', initialiseCMSIfAForumLoaded());
}
});

View File

@ -0,0 +1,187 @@
/**
* Manages searching and displaying of datarecords speccified in GenericDataAdmin.
*/
LeftPane = Class.create();
LeftPane.applyTo('#LeftPane');
LeftPane.prototype = {
effectDuration: .7,
initialize: function() {
Behaviour.register({
'#Form_CreationForm_action_createRecord': {
onclick: this.addNewRecord.bind(this)
},
'#Form_SearchForm_action_getResults': {
onclick: this.filterOnChange.bind(this)
},
/**
* Small hack to keep forms for search and resultActions separate
* in order to avoid DOM-id-duplicates.
*/
'#Form_ExportForm_action_export': {
onclick: function(e) {
var el = Event.element(e);
var origAction = $('Form_SearchForm').action;
$('Form_SearchForm').action = origAction + "&" + el.name + "=1";
$('Form_SearchForm').submit();
$('Form_SearchForm').action = origAction;
Event.stop(e);
return false;
}.bind(this)
}
});
this.initAdvancedSearch();
},
/**
* If a toggle-link is present, provide functionality to show/hide additional search-options.
*/
initAdvancedSearch: function() {
var advancedSearch = $('AdvancedSearchFields');
if(!advancedSearch) {
return false;
}
advancedSearch.style.display = "none";
var toggleLinks = document.getElementsBySelector('div#LeftPane .ToggleAdvancedSearchFields');
if(toggleLinks && toggleLinks[0]) toggleLinks[0].style.display = "block";
Behaviour.register({
"div#LeftPane .ToggleAdvancedSearchFields a":{
onclick: function(e){
var el = Event.element(e);
var advancedSearchDiv = $('AdvancedSearchFields');
if(advancedSearchDiv.style.display == 'none') {
Effect.SlideDown(advancedSearchDiv,{duration:this.effectDuration});
el.firstChild.nodeValue = el.firstChild.nodeValue.replace(/Show/,"Hide");
} else {
Effect.SlideUp(advancedSearchDiv,{duration:this.effectDuration});
el.firstChild.nodeValue = el.firstChild.nodeValue.replace(/Hide/,"Show");
}
Event.stop(e);
return false;
}.bind(this)
}
});
},
/**
* @param form DOM-Element Needed if you invoke this action externally
*/
addNewRecord: function(e, form) {
var el = Event.element(e);
var form = (form) ? form : Event.findElement(e,"form");
var link = form.action + "&" + Form.serialize(form);
var openTab = $('Form_EditForm').getCurrentTab();
var callback = function() {
statusMessage("Record created","good");
}
$('Form_EditForm').updateCMSContent(el, openTab, link, callback);
Event.stop(e);
return false;
},
displayNewRecord: function(response) {
$('Form_EditForm').innerHTML = response.responseText;
onload_init_tabstrip();
// Makes sure content-behaviour is correctly initialized in the main window.
Behaviour.apply( $('Form_EditForm') );
},
filterOnChange: function (e, custom_onComplete) {
try {
// hide existing results
$("ResultTable_holder").innerHTML = "";
// show loading indicator
showIndicator('SearchLoading', $$('#Form_SearchForm p.Actions')[0]);
} catch(e) {}
Ajax.SubmitForm(
"Form_SearchForm",
"action_getResults",
{
postBody : 'ajax=1',
onSuccess : this.updateResult.bind(this),
onFailure : function(response) {
errorMessage('Error encountered during search', response);
},
onComplete : custom_onComplete
}
);
Event.stop(e);
return false;
},
updateResult: function(response){
// hide loading indicator
hideIndicator('SearchLoading');
var holder;
if(holder = $('ResultTable_holder')){
holder.innerHTML = response.responseText;
Behaviour.apply( $('ResultTable_holder') );
}
},
clearFilter: function() {
var searchForm = $('Form_SearchForm');
var fields = searchForm.getElementsByTagName('input');
for(var i=0; i<fields.length; i++){
fields[0].value = '';
}
}
}
/**
* Shows results provided by LeftPane.
*/
ResultTable = Class.create();
ResultTable.applyTo('#ResultTable_holder');
ResultTable.prototype = {
initialize: function() {
Behaviour.register({
"#LeftPane a.show":{
onclick: function(e){
var openTab = $('Form_EditForm').getCurrentTab();
$('Form_EditForm').updateCMSContent(this, openTab);
Event.stop(e);
return false;
}
},
"#ResultTable_holder a":{
onclick: function(e){
var el = Event.element(e);
var link = (el.nodeName == "A") ? el : document.getElementsBySelector("a.show",el)[0];
if(link) {
var openTab = $('Form_EditForm').getCurrentTab();
$('Form_EditForm').updateCMSContent(link, openTab);
}
Event.stop(e);
return false;
}
}
});
}
}
function fixHeight_left() {
fitToParent('LeftPane');
fitToParent('Search_holder',12);
if($('SideTabs')) {
$('SideTabs').resize();
}
}

View File

@ -0,0 +1,116 @@
/**
* Loads, views and removes records in the main view.
*/
RightContent = Class.create();
RightContent.applyTo('#Form_EditForm');
RightContent.prototype = {
welcomeMessage: "<h1>SilverStripe CMS</h1><p>Welcome to SilverStripe CMS! Please choose click on one of the items on the left pane.</p>",
initialize : function() {
Behaviour.register({
'#Form_EditForm_action_delete' : {
onclick: this.remove.bind(this)
}
});
},
updateCMSContent: function(el, currentTab, link, customCallBack) {
if(!customCallBack) customCallBack = function(){};
if(el || link){
var reqLink = (el.href) ? el.href : link;
if(typeof(currentTab) != 'undefined')
$('Form_EditForm').openTab = currentTab;
statusMessage("loading...", null, true);
new Ajax.Request(reqLink, {
asynchronous : true,
postBody : 'ajax=1',
onSuccess: customCallBack.bind(this),
onComplete : this.successfullyReceivedPage.bind(this),
onFailure : function(response) {
errorMessage('Error loading page',response);
}
});
}else{
$('Form_EditForm').innerHTML = this.welcomeMessage;
}
},
successfullyReceivedPage : function(response) {
$('Form_EditForm').loadNewPage(response.responseText);
$('Form_EditForm').initialize();
onload_init_tabstrip();
// TODO
// try to reopen saved tab (before ajax request)
if($('Form_EditForm').openTab) {
openTab($('Form_EditForm').openTab);
} else {
$('Form_EditForm').openTab = this.getCurrentTab();
openTab($('Form_EditForm').openTab);
}
clearStatusMessage();
},
remove: function(e) {
if(window.confirm('Are you sure you want to delete?')){
var el = Event.element(e);
Ajax.SubmitForm(el.ownerForm, el.name, {
postBody : 'ajax=1',
onSuccess: Ajax.Evaluator,
onFailure: ajaxErrorHandler
});
}
Event.stop(e);
return false;
},
deleteEffect: function() {
new Effect.Fade(this, {duration:2});
window.setTimeout(function() {
$('Form_EditForm').updateCMSContent();
new Effect.Appear($('Form_EditForm'), {duration:3});
}, 3000);
},
tabExists: function(tabName) {
if($('Root')){
var tags = $('Root').getElementsByTagName('li');
for(var i=0; i<tags.length; i++){
var current = tags[i].getElementsByTagName('a')[0];
var match = current.id.match(/tab-(.+)$/);
if(match[0] == tabName) {
return true;
}
}
}
return false;
},
getCurrentTab: function() {
var current;
if($('Root')){
var tags = $('Root').getElementsByTagName('li');
for(var i=0; i<tags.length; i++){
if(Element.hasClassName(tags[0], 'current')){
current = tags[i].getElementsByTagName('a')[0];
}else{
current = tags[0].getElementsByTagName('a')[0]
}
current.id.match(/tab-(.+)$/);
return RegExp.$1;
}
}
},
setTitle: function(title) {
var titleHolder = $$('div#right .title div');
if(titleHolder[0]) {
titleHolder[0].innerHTML = title;
}
}
}

84
javascript/ImprintStats.js Executable file
View File

@ -0,0 +1,84 @@
/********************************************************
* Copyright Nexsys Development Ltd 2002 - 2004 *
* Version 1.7 *
* http://www.nexsysdev.com *
********************************************************/
var NI_SE = "", NI_SC = "", NI_ST = "", NI_SV = "", NI_IW = 0;
function ni_TrackHit(server, siteCode, description, section, service, trigger, amount, adCampaign, title, url, layer, basketAdd, basketRemove, parameters)
{
NI_SE = server; NI_SC = siteCode; NI_ST = section; NI_SV = service;
function CB()
{
var cb="", key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for (i=0;i<5;i++) cb += key.charAt(Math.floor(Math.random()*52));
return cb;
}
function A(B,C)
{
if (typeof(C) != "undefined" && C != "") return "&"+B+"="+escape(C);
else return "";
}
if (typeof(NI_PAGE) != "undefined")
{
if (url.indexOf("?") > 0) url += "&ni_page=" + NI_PAGE;
else url += "?ni_page=" + NI_PAGE;
}
var p = "http"+(document.URL.indexOf('https:')==0?'s':'');
var t = new Date();
var u = p+"://"+server+"/Hit.aspx?tv=1&sc="+siteCode;
u+=A("lo",description);
u+=A("du",url);
u+=A("st",section);
u+=A("sv",service);
u+=A("ac",adCampaign);
u+=A("tr",trigger);
u+=A("ta",amount);
u+=A("ti",title);
u+=A("tz",t.getTimezoneOffset());
u+=A("ch",t.getHours());
u+=A("cb",CB());
u+=A("ru",window.document.referrer);
u+=A("js","1");
u+=A("ul",navigator.appName=="Netscape" ? navigator.language : navigator.userLanguage);
u+=A("ba", basketAdd);
u+=A("br", basketRemove);
u+=A("pm", parameters);
if (typeof(screen)=="object")
{
u+=A("sr",screen.width+"x"+screen.height);
}
if (layer == 1)
{
if (NI_IW == 0) { document.write('<div style="position:absolute;width:1px;height:1px;overflow:hidden"><IMG name="ni_tag" id="ni_tag" border="0" width="1" height="1" src="'+u+'"></div>'); NI_IW = 1; }
else { u+=A("ir","1"); document.images.ni_tag.src = u; }
}
else
{
document.write('<IMG name="ni_tag" id="ni_tag" border="0" width="1" height="1" src="'+u+'">'); NI_IW = 2;
}
}
/* The following function may be used any number of times in a page to load a file and track a hit
* against that file. This is useful when the file being loaded is not html,
* or is not under your control, so can't have an imprint tracking code inserted into it.
*
* E.g. <a href="javascript:ni_LoadUrl('Catalogue.pdf', 'Catalogue')">Download catalogue</a>
*
* If you consider clicking on the link to be the completion of a transaction, use the 'Sale' trigger
* E.g. <a href="javascript:ni_LoadUrl('Catalogue.pdf', 'Catalogue', 'Sale')">Download catalogue</a>
*/
function ni_LoadUrl(url, title, trigger)
{
ni_TrackHit(NI_SE, NI_SC, "", NI_ST, NI_SV, trigger, "", "", title, url, NI_IW, "", "", "");
document.location.href = url;
}

816
javascript/LeftAndMain.js Normal file
View File

@ -0,0 +1,816 @@
var _AJAX_LOADING = false;
/**
* Code for the separator bar between the two panes
*/
function DraggableSeparator() {
this.onmousedown = this.onmousedown.bindAsEventListener(this);
// this.onselectstart = this.onselectstart.bindAsEventListener(this);
}
DraggableSeparator.prototype = {
onmousedown : function(event) {
this.leftBase = $('left').offsetWidth - Event.pointerX(event);
this.separatorBase = getDimension($('separator'),'left') - Event.pointerX(event);
this.rightBase = getDimension($('right'),'left') - Event.pointerX(event);
document.onmousemove = this.document_mousemove.bindAsEventListener(this);
document.onmouseup = this.document_mouseup.bindAsEventListener(this);
document.body.onselectstart = this.body_selectstart.bindAsEventListener(this);
},
document_mousemove : function(event) {
$('left').style.width = (this.leftBase + Event.pointerX(event)) + 'px';
fixRightWidth();
},
document_mouseup : function(e) {
document.onmousemove = null;
},
body_selectstart : function(event) {
Event.stop(event);
return false;
}
}
function fixRightWidth() {
if( !$('right') )
return;
// Absolutely position all the elements
var sep = getDimension($('left'),'width') + getDimension($('left'),'left');
$('separator').style.left = (sep + 2) + 'px';
$('right').style.left = (sep + 6) + 'px';
// Give the remaining space to right
var rightWidth = parseInt(document.body.offsetWidth) - parseInt($('left').offsetWidth) - $('separator').offsetWidth - 8;
if( rightWidth >= 0 )
$('right').style.width = rightWidth + 'px';
var rb;
if(rb = $('rightbottom')) {
rb.style.left = $('right').style.left;
rb.style.width = $('right').style.width;
}
}
Behaviour.register({
'#separator' : DraggableSeparator,
'#left' : {
hide : function() {
if(!this.hidden) {
this.hidden = true;
this.style.width = null;
Element.addClassName(this,'hidden');
Element.addClassName('separator','hidden');
fixRightWidth();
}
},
show : function() {
if(this.hidden) {
this.hidden = false;
Element.removeClassName(this,'hidden');
Element.removeClassName('separator','hidden');
fixRightWidth();
}
}
},
'#MainMenu li' : {
onclick : function(event) {
window.location.href = this.getElementsByTagName('a')[0].href;
Event.stop(event);
}
},
'#Menu-help' : {
onclick : function() {
var w = window.open(this.getElementsByTagName('a')[0].href, 'help');
w.focus();
return false;
}
}
})
window.ontabschanged = function() {
var formEl = $('Form_EditForm');
if( !formEl )
return;
var fs = formEl.getElementsByTagName('fieldset')[0];
if(fs) fs.style.height = formEl.style.height;
// var divs = document.getElementsBySelector('#Form_EditForm div');
/*for(i=0;i<divs.length;i++) {
if( ( Element.hasClassName(divs[i],'tab') || Element.hasClassName(divs[i],'tabset') ) && isVisible(divs[i]) ) {
if(navigator.appName == "Microsoft Internet Explorer")
fitToParent(divs[i], i == 0 ? 18 : 0);
else
fitToParent(divs[i], 3);
}
}*/
if( _TAB_DIVS_ON_PAGE ) {
for(i = 0; i < _TAB_DIVS_ON_PAGE.length; i++ ) {
// console.log(_TAB_DIVS_ON_PAGE[i].id);
if(navigator.appName == "Microsoft Internet Explorer")
fitToParent(_TAB_DIVS_ON_PAGE[i], i == 0 ? 18 : 30);
else
fitToParent(_TAB_DIVS_ON_PAGE[i], /*3*/ 30);
}
}
}
window.onresize = function(init) {
var right = $('right');
var rightbottom = $('rightbottom');
if(rightbottom) rightbottom.style.display = 'none';
if(typeof fitToParent == 'function') fitToParent('right', 12);
if( $('left') && $('separator') && right )
$('left').style.height = $('separator').style.height = right.style.height;
if(rightbottom) {
var newHeight = (parseInt(right.style.height) / 3);
right.style.height = (newHeight*2) + 'px';
rightbottom.style.height = newHeight + 'px';
rightbottom.style.display = '';
rightbottom.style.top = getDimension(right,'top') + (newHeight*2) + 'px';
}
if(typeof fitToParent == 'function') fitToParent('Form_EditForm');
if(typeof fixHeight_left == 'function') fixHeight_left();
if(typeof fixRightWidth == 'function') fixRightWidth();
window.ontabschanged();
}
appendLoader(function() {
document.body.style.overflow = 'hidden';
window.onresize(true);
});
function isVisible(el) {
// if(typeof el.isVisible != 'undefined') return el.isVisible;
if(el.tagName == "body" || el.tagName == "BODY") return (el.isVisible = true);
else if(el.style.display == 'none') return (el.isVisible = false);
else return (el.isVisible = isVisible(el.parentNode));
}
LeftAndMain_window_unload = function() {
window.exiting = true; // this is used by prototype
if(typeof autoSave == 'function')
autoSave(navigator.appName == "Microsoft Internet Explorer");
}
Event.observe(window, 'beforeunload', LeftAndMain_window_unload);
/**
* Unlock the locked status message.
* Show a queued message, if one exists
*/
function unlockStatusMessage() {
statusMessage.locked = false;
if(statusMessage.queued) {
statusMessage(
statusMessage.queued.msg,
statusMessage.queued.type,
statusMessage.queued.showNetworkActivity);
statusMessage.queued = null;
}
}
/**
* Move form actions to the top and make them ajax
*/
function ajaxActionsAtTop(formName, actionHolderName, tabName) {
var actions = document.getElementsBySelector('#' + formName + ' .Actions')[0];
var holder;
if(actions) {
if((holder = $(actionHolderName)) && holder != actions) {
holder.parentNode.removeChild(holder);
}
actions.id = actionHolderName;
actions.className = 'ajaxActions';
$(tabName).appendChild(actions);
prepareAjaxActions(actions, formName, tabName);
}
}
/**
* Prepare the ajax actions so that the buttons actually do something
*/
function prepareAjaxActions(actions, formName, tabName) {
var i, button, list = actions.getElementsByTagName('input')
for (i=0;button=list[i];i++) {
button.ownerForm = $(formName);
button.onclick = function(e) {
// tries to call a custom method of the format "action_<youraction>_right"
if(window[this.name + '_' + tabName]) {
window[this.name + '_' + tabName](e);
} else {
statusMessage(ingize(this.value));
Ajax.SubmitForm(this.ownerForm, this.name, {
onSuccess: Ajax.Evaluator,
onFailure: ajaxErrorHandler
});
}
return false;
}
behaveAs(button, StatusTitle);
}
}
function ingize(val) {
if(!val) val = "process";
if(val.substr(val.length-1) == 'e') return val.substr(0, val.length-1) + 'ing...';
else return val + 'ing...';
}
/**
* Submit the given form and evaluate the Ajax response.
* Needs to be bound to an object with the following parameters to work:
* - form
* - action
* - verb
*
* The bound function can then be called, with the arguments passed
*/
function ajaxSubmitForm(automated, callAfter, form, action, verb) {
// tinyMCE.triggerSave(true);
var alreadySaved = false;
if($(form).elements.length < 2) alreadySaved = true;
if(alreadySaved) {
if(callAfter) callAfter();
} else {
statusMessage(verb + '...', '', true);
var success = function(response) {
Ajax.Evaluator(response);
if(callAfter) callAfter();
}
if(callAfter) success = success.bind({callAfter : callAfter});
Ajax.SubmitForm(form, action, {
onSuccess : success,
onFailure : function(response) {
errorMessage('Error ' + verb, response);
}
});
}
return false;
};
/**
* Post the given fields to the given url
*/
function ajaxSubmitFieldSet(href, fieldSet, extraData) {
// Build data
var i,field,data = "ajax=1";
for(i=0;field=fieldSet[i];i++) {
data += '&' + Form.Element.serialize(field);
}
if(extraData){
data += '&'+extraData;
}
// Send request
new Ajax.Request(href, {
method : 'post', postBody : data,
onSuccess : function(response) {
//alert(response.responseText);
Ajax.Evaluator(response);
},
onFailure : function(response) {
alert(response.responseText);
//errorMessage('Error: ', response);
}
});
}
/**
* Post the given fields to the given url
*/
function ajaxLink(href) {
// Send request
new Ajax.Request(href + (href.indexOf("?") == -1 ? "?" : "&") + "ajax=1", {
method : 'get',
onSuccess : Ajax.Evaluator,
onFailure : ajaxErrorHandler
});
}
/**
* Load a URL into the given form
*/
function ajaxLoadPage() {
statusMessage('loading...', 2, true);
new Ajax.Request(this.URL + '&ajax=1', {
method : 'get',
onSuccess : ajaxLoadPage_success.bind(this)
});
}
function ajaxLoadPage_success(response) {
statusMessage('loaded');
$(this.form).loadNewPage(response.responseText);
}
/**
* Behaviour of the statuts message.
*/
Behaviour.register({
'#statusMessage' : {
showMessage : function(message, type, waitTime, clearManually) {
if(this.fadeTimer) {
clearTimeout(this.fadeTimer);
this.fadeTimer = null;
}
if(this.currentEffect) {
this.currentEffect.cancel();
this.currentEffect = null;
}
this.innerHTML = message;
this.className = type;
Element.setOpacity(this, 1);
this.style.position = 'absolute';
this.style.display = '';
this.style.visibility = '';
if(!clearManually) {
this.fade(0.5,waitTime ? waitTime : 5);
}
},
clearMessage : function(waitTime) {
this.fade(0.5, waitTime);
},
fade: function(fadeTime, waitTime) {
if(!fadeTime) fadeTime = 0.5;
// Wait a bit before fading
if(waitTime) {
this.fadeTimer = setTimeout((function() {
this.fade(fadeTime);
}).bind(this), waitTime * 1000);
// Fade straight away
} else {
this.currentEffect = new Effect.Opacity(this,
{ duration: 0.5,
transition: Effect.Transitions.linear,
from: 1.0, to: 0.0,
afterFinish : this.afterFade.bind(this) });
}
},
afterFade : function() {
this.style.visibility = 'hidden';
this.style.display = 'none';
this.innerHTML = '';
}
}
});
/**
* Show a status message.
*
* @param msg String
* @param type String (optional) can be 'good' or 'bad'
* @param clearManually boolean Don't automatically fade message.
*/
function statusMessage(msg, type, clearManually) {
var statusMessageEl = $('statusMessage');
if(statusMessageEl) {
if(msg) {
statusMessageEl.showMessage(msg, type, msg.length / 20, clearManually);
} else {
statusMessageEl.clearMessage();
}
}
}
function clearStatusMessage() {
$('statusMessage').clearMessage();
}
/**
* Called when something goes wrong
*/
function errorMessage(msg, fullMessage) {
// More complex error for developers
if(fullMessage && window.location.href.indexOf('//dev') != -1) {
// Get the message from an Ajax response object
try {
if(typeof fullMessage == 'object') fullMessage = fullMessage.status + '//' + fullMessage.responseText;
} catch(er) {
fullMessage = "";
}
msg = msg + '<br>' + fullMessage.replace(/\n/g,'<br>');
}
$('statusMessage').showMessage(msg,'bad',60);
}
function ajaxErrorHandler(response) {
errorMessage('Error talking to server', response);
}
/**
* Applying StatusTitle to an element will mean that the title attribute is shown as a statusmessage
* upon hover
*/
StatusTitle = Class.create();
StatusTitle.prototype = {
onmouseover : function() {
if(this.title) {
this.message = this.title;
this.title = null;
}
if(this.message) {
$('statusMessage').showMessage(this.message);
}
},
onmouseout : function() {
if(this.message) {
$('statusMessage').fade(0.3,1);
}
}
}
/**
* BaseForm is the base form class used in the CMS.
*/
BaseForm = Class.create();
BaseForm.prototype = {
intitialize: function() {
this.visible = this.style.display == 'none' ? false : true;
// Collect all the buttons and attach handlers
this.buttons = [];
var i,input,allInputs = this.getElementsByTagName('input');
for(i=0;input=allInputs[i];i++) {
if(input.type == 'button' || input.type == 'submit') {
this.buttons.push(input);
input.holder = this;
input.onclick = function() { return this.holder.buttonClicked(this); }
}
}
},
show: function() {
this.visible = true;
Element.hide(show);
},
hide: function() {
this.visible = false;
Element.hide(this);
},
isVisible: function() {
return this.visible;
},
buttonClicked: function(button) {
return true;
}
}
/**
* ChangeTracker is a class that can be applied to forms to support change tracking on forms.
*/
ChangeTracker = Class.create();
ChangeTracker.prototype = {
initialize: function() {
this.resetElements();
},
/**
* Reset all the 'changed field' data.
*/
resetElements: function(debug) {
var elements = Form.getElements(this);
var i, element;
for(i=0;element=elements[i];i++) {
// Initialise each element
if(element.resetChanged) {
element.resetChanged();
} else {
element.originalSerialized = Form.Element.serialize(element);
}
}
},
field_changed: function() {
return this.originalSerialized != Form.Element.serialize(this);
},
/**
* Returns true if something in the form has been changed
*/
isChanged: function() {
var elements = Form.getElements(this);
var i, element;
for(i=0;element=elements[i];i++) {
if(!element.isChanged) element.isChanged = this.field_changed;
if(!this.changeDetection_fieldsToIgnore[element.name] && element.isChanged()) {
//if( window.location.href.match( /^https?:\/\/dev/ ) )
// Debug.log('Changed:'+ element.id + '(' + this.originalSerialized +')->('+Form.Element.serialize(element)+')' );
return true;
}
}
return false;
},
changeDetection_fieldsToIgnore : {
'Sort' : true
},
/**
* Serialize only the fields to change.
* You can specify the names of fields that must be included as arguments
*/
serializeChangedFields: function() {
var elements = Form.getElements(this);
var queryComponent, queryComponents = new Array();
var i, element;
var forceFields = {};
if(arguments) {for(var i=0;i<arguments.length;i++) forceFields[arguments[i]] = true;}
for(i=0;element=elements[i];i++) {
if(!element.name.match(/^action_(.+)$/i)) // For dropdown those 'action_xxx' fields.
{ if(!element.isChanged) element.isChanged = this.field_changed;
if(forceFields[element.name] || (element.isChanged()) || element.name.match(/\[.*\]/g) ) {
queryComponent = Form.Element.serialize(element);
if (queryComponent)
queryComponents.push(queryComponent);
} else {
// Used by the Sapphire code to preserve the form field value
if( element.name.match( '/\]$/' ) )
queryComponents.push(element.name.substring( 0, element.name.length - 1 ) + '_unchanged' + ']=1' );
else
queryComponents.push(element.name + '_unchanged=1');
}
}
}
//alert(queryComponents.join('&'));
return queryComponents.join('&');
},
/**
* Serialize all the fields on the page
*/
serializeAllFields: function() {
return Form.serializeWithoutButtons(this);
}
}
/*
* ModalForm provides a form with the functionality to prevent certian actions from occurring until
* it's been closed.
*
* To use, You should run the blockEvent method as many times as needed.
*/
ModalForm = Class.extend('BaseForm');
ModalForm.prototype = {
/*
* Prevent the given event from occurring on the given event while the form is open.
* These must be CMS-created events, not built-in javascript events.
* For example, form.blockEvent($('sitetree'), 'SelectionChanged')
*/
blockEvent: function(element, event) {
element.observeMethod(event, (function() { return !this.isVisible();}).bind(this) );
}
}
function doYouWantToRollback(handlers) {
var url = document.getElementsByTagName('base')[0].href + 'admin/canceldraftchangesdialog';
OpenModalDialog(url, handlers, 'Are you sure?' );
}
/**
* Shows a "do you want to save" dialog box.
* Returns save / discard / cancel
*/
var _DO_YOU_WANT_TO_SAVE_IS_OPEN = false;
function doYouWantToSave(handlers) {
// modalDialog('admin/dialog?Message=You+have+changed+this+page.+Do+you+want+to+save+these+changes%3F&Buttons[]=save,Save+changes&Buttons[]=discard,Discard+changes&Buttons[]=cancel,Cancel', handlers);
var url = document.getElementsByTagName('base')[0].href + 'admin/savedialog';
OpenModalDialog(url, handlers, 'Unsaved Changes' );
}
function modalDialog(url, handlers) {
var baseURL = document.getElementsByTagName('base')[0].href;
if(window.showModalDialog) {
var result = showModalDialog(baseURL + url + '&Modal=1', null, "status:no;dialogWidth:400px;dialogHeight:150px;edge:sunken");
if(handlers[result])
handlers[result]();
} else {
_DO_YOU_WANT_TO_SAVE_IS_OPEN = true;
doYouWantToSave.dialog = new ModalDialog(baseURL + url, handlers);
}
}
ModalDialog = Class.create();
ModalDialog.prototype = {
initialize: function(url, handlers) {
this.url = url;
this.handlers = handlers;
this.timer = setInterval(this.interval.bind(this), 50);
this.window = window.open(this.url, 'dialog', "status=no,width=400,height=150,edge=sunken");
this.window.dialogObject = this;
this.window.linkedObject = this;
setTimeout( (function(){this.window.linkedObject = this;}).bind(this), 500);
},
force: function (val) {
this.finished = true;
this.clearInterval(this.time);
if(this.handlers[val]) {
_DO_YOU_WANT_TO_SAVE_IS_OPEN = false;
(this.handlers[val])();
} else {
throw("Couldn't find a handler called '" + this.result + "'");
}
},
interval: function() {
if(this.finished) {
clearInterval(this.timer);
return;
}
if(!this.window || this.window.closed) {
clearInterval(this.timer);
if(this.handlers) {
if(this.handlers[this.result]) {
_DO_YOU_WANT_TO_SAVE_IS_OPEN = false;
(this.handlers[this.result])();
} else {
throw("Couldn't find a handler called '" + this.result + "'");
}
}
} else {
this.window.focus();
}
}
}
window.top._OPEN_DIALOG = null;
OpenModalDialog = function( url, handlers, message ) {
var dialog = new GBModalDialog( url, handlers, message );
}
GBModalDialog = Class.create();
GBModalDialog.prototype = {
initialize: function( url, handlers, message ) {
this.url = url;
this.handlers = handlers;
this.caption = message;
window.top._OPEN_DIALOG = this;
GB_show( this.caption, this.url, 110, 450 );
},
execHandler: function( handler ) {
GB_hide();
if( this.handlers[handler] )
this.handlers[handler]();
else
throw( "Unknown handler '" + handler + "'" );
}
}
function hideLoading() {
$('Loading').style.display = 'none';
document.body.className = '';
}
function baseHref() {
var baseTags = document.getElementsByTagName('base');
if(baseTags) return baseTags[0].href;
else return "";
}
returnFalse = function() {
return false;
}
showResponseAsSuccessMessage = function(response) {
statusMessage(response.responseText, 'good');
}
/**
* This function is called by prototype when it receives notification that the user was logged out.
* It redirects back to the login form.
*/
function onSessionLost() {
alert("You've been logged out of the server, so we're going to send you back to the log-in screen.");
window.location.href = baseHref() + 'Security/login?BackURL=' + encodeURIComponent(window.location.href);
}
var _CURRENT_CONTEXT_MENU = null;
/**
* Create a new context menu
* @param event The event object
* @param owner The DOM element that this context-menu was requested from
* @param menuItems A map of title -> method; context-menu operations to get called
*/
function createContextMenu(event, owner, menuItems) {
if(_CURRENT_CONTEXT_MENU) {
document.body.removeChild(_CURRENT_CONTEXT_MENU);
_CURRENT_CONTEXT_MENU = null;
}
var menu = document.createElement("ul");
menu.className = 'contextMenu';
menu.style.position = 'absolute';
menu.style.left = event.clientX + 'px';
menu.style.top = event.clientY + 'px';
var menuItemName, menuItemTag, menuATag;
for(menuItemName in menuItems) {
menuItemTag = document.createElement("li");
menuATag = document.createElement("a");
menuATag.href = "#";
menuATag.onclick = menuATag.oncontextmenu = contextmenu_onclick;
menuATag.innerHTML = menuItemName;
menuATag.handler = menuItems[menuItemName];
menuATag.owner = owner;
menuItemTag.appendChild(menuATag);
menu.appendChild(menuItemTag);
}
document.body.appendChild(menu);
document.body.onclick = contextmenu_close;
_CURRENT_CONTEXT_MENU = menu;
return menu;
}
function contextmenu_close() {
if(_CURRENT_CONTEXT_MENU) {
document.body.removeChild(_CURRENT_CONTEXT_MENU);
_CURRENT_CONTEXT_MENU = null;
}
}
function contextmenu_onclick() {
this.handler(this.owner);
contextmenu_close();
return false;
}
/**
* Shows an ajax loading indicator.
*
* @param id String Identifier for the newly created image
* @param container ID/DOM Element
* @param imgSrc String (optional)
* @param insertionType Object (optional) Prototype-style insertion-classes, defaults to Insertion.Bottom
* @param displayType String (optional) "inline" or "block"
*/
function showIndicator(id, container, imgSrc, insertionType, displayType) {
if(!id || !$(container)) return false;
if(!imgSrc) imgSrc = "cms/images/network-save.gif";
if(!displayType) displayType = "inline";
if(!insertionType) insertionType = Insertion.Bottom;
if(!$(id)) {
var html = '<img src="' + imgSrc + '" class="indicator ' + displayType + '" id="' + id + '" style="display: none" />';
new insertionType(container, html);
}
Effect.Appear(id);
}
function hideIndicator(id) {
Effect.Fade(id, {duration: 0.3});
}

475
javascript/LeftAndMain_left.js Executable file
View File

@ -0,0 +1,475 @@
/**
* LeftAndMain_left.js
* Code for supporting the left-hand panel of all the 2-pane admin windows
* This includes code for the action panel at the top, and the draggable, ajax-linked tree.
*/
if(typeof SiteTreeHandlers == 'undefined') SiteTreeHandlers = {};
SiteTreeHandlers.parentChanged_url = 'admin/ajaxupdateparent';
SiteTreeHandlers.orderChanged_url = 'admin/ajaxupdatesort';
SiteTreeHandlers.showRecord_url = 'admin/show/';
var _HANDLER_FORMS = {
addpage : 'Form_AddPageOptionsForm',
deletepage : 'deletepage_options',
sortitems : 'sortitems_options'
};
/**
* Overload this with a real context menu if necessary
*/
var TreeContextMenu = null;
/**
* Extra methods for the tree when used in the LHS of the CMS
*/
TreeAPI = Class.create();
TreeAPI.prototype = {
/**
* Perform the given code on the each tree node with the given index.
* There could be more than one :-)
* @param idx The index of the tree node
* @param code A method to be executed, that will be passed the tree node
*/
performOnTreeNode: function(idx, code) {
var treeNode = this.getTreeNodeByIdx(idx);
if(!treeNode) return;
if(treeNode.className.indexOf('manyparents') == -1) {
code(treeNode);
} else {
var i,item,allNodes = this.getElementsByTagName('li');
for(i=0;item=allNodes[i];i++) {
if(treeNode.id == item.id) code(item);
}
}
},
getTreeNodeByIdx : function(idx) {
if(!idx) idx = "0";
var node = document.getElementById('record-' + idx);
if(idx == "0" && !node) node = document.getElementById('record-root');
return node;
},
getIdxOf : function(treeNode) {
if(treeNode && treeNode.id && treeNode.id.match(/record-([0-9a-zA-Z\-]+)$/))
return RegExp.$1;
},
createTreeNode : function(idx, title, pageType) {
var i;
var node = document.createElement('li');
node.id = 'record-' + idx;
node.className = pageType;
var aTag = document.createElement('a');
aTag.href = SiteTreeHandlers.showRecord_url + idx;
aTag.innerHTML = title;
node.appendChild(aTag);
SiteTreeNode.create(node, this.options);
return node;
},
setNodeIdx : function(idx, newIdx) {
this.performOnTreeNode(idx, function(treeNode) {
treeNode.id = 'record-' + newIdx;
var aTag = treeNode.getElementsByTagName('a')[0];
aTag.href = aTag.href.replace(idx, newIdx);
});
var treeNode = this.getTreeNodeByIdx(idx);
},
setNodeTitle : function(idx, title) {
this.performOnTreeNode(idx, function(treeNode) {
var aTag = treeNode.getElementsByTagName('a')[0];
aTag.innerHTML = title;
});
},
setNodeIcon: function(idx, oldClassName, newClassName) {
this.performOnTreeNode(idx, function(treeNode) {
treeNode.className = treeNode.className.replace(oldClassName,newClassName);
treeNode.aSpan.className = 'a ' + treeNode.className.replace('closed','spanClosed');
treeNode.setIconByClass();
});
},
setCurrentByIdx : function(idx) {
if(this.selected) {
var i,item;
for(i=0;item=this.selected[i];i++) {
item.removeNodeClass('current');
}
}
__tree = this;
__tree.selected = [];
this.performOnTreeNode(idx, function(treeNode) {
__tree.selected.push(treeNode);
treeNode.expose();
treeNode.addNodeClass('current');
});
},
changeCurrentTo : function(newNode) {
if(this.selected) {
var i,item;
for(i=0;item=this.selected[i];i++) {
item.removeNodeClass('current');
}
}
newNode.addNodeClass('current');
this.selected = [newNode];
newNode.expose();
},
firstSelected : function() {
if(this.selected) return this.selected[0];
}
};
/**
* Extra methods for the tree node when used in the LHS of the CMS
*/
TreeNodeAPI = Class.create();
TreeNodeAPI.prototype = {
selectTreeNode : function() {
if(this.getElementsByTagName('a')[0].href) {
_AJAX_LOADING = true;
if($('sitetree').notify('SelectionChanged', this)) {
autoSave(true, this.getPageFromServer.bind(this));
}
}
},
getPageFromServer : function() {
$('Form_EditForm').getPageFromServer(this.id.replace('record-',''), this);
_AJAX_LOADING = false;
},
ajaxExpansion : function() {
this.addNodeClass('loading');
var ul = this.treeNodeHolder(false);
ul.innerHTML = 'loading...';
// Any attempts to add children to this page should, in fact, cue them up for insertion later
ul.cuedNewNodes = [];
ul.appendTreeNode = function(node) {
this.cuedNewNodes[this.cuedNewNodes.length] = node;
}
new Ajax.Request(SiteTreeHandlers.loadTree_url + '&ajax=1&ID=' + this.getIdx(), {
onSuccess : this.installSubtree.bind(this),
onFailure : this.showSubtreeLoadingError
});
},
showSubtreeLoadingError: function(response) {
errorMessage('error loading subtree', response);
},
/**
* Context menu
*/
oncontextmenu: function(event) {
if(TreeContextMenu) {
if(!event) event = window.event;
createContextMenu(event, this, TreeContextMenu);
Event.stop(event);
return false;
}
}
}
/**
* In the case of Tree & DraggableTree, the root tree and the sub-trees all use the same class.
* In this case, however, SiteTree has a much bigger API and so SiteSubTree is smaller.
*/
SiteSubTree = Class.extend('Tree').extend('TreeAPI');
SiteSubTree.prototype = {
castAsTreeNode: function(li) {
behaveAs(li, SiteTreeNode, this.options);
}
}
/**
* Our SiteTree class extends the tree object with a richer manipulation API.
* The server will send a piece javascript that uses these functions. In this way, the server
* has flexibility over its operation, but the Script->HTML interface is kept client-side.
*/
SiteTree = Class.extend('SiteSubTree');
SiteTree.prototype = {
initialize : function() {
this.Tree.initialize();
/*
if(!this.tree.selected) this.tree.selected = [];
var els = this.getElementsByTagName('li');
for(var i=0;i<els.length;i++) if(els[i].className.indexOf('current') > -1) {
this.tree.selected.push(els[i]);
break;
}
*/
this.observeMethod('SelectionChanged', this.interruptLoading.bind(this) );
},
destroy: function () {
if(this.Tree) this.Tree.destroy();
this.Tree = null;
this.SiteSubTree = null;
this.TreeAPI = null;
this.selected = null;
},
/**
* Stop the currently loading node from loading.
*/
interruptLoading: function( newLoadingNode ) {
if( this.loadingNode ) this.loadingNode.removeNodeClass('loading');
this.loadingNode = newLoadingNode;
}
}
SiteTreeNode = Class.extend('TreeNode').extend('TreeNodeAPI');
SiteTreeNode.prototype = {
initialize: function(options) {
this.TreeNode.initialize(options);
if(this.className && this.className.match(/ *([^ ]+)( +|$)/)) {
if((typeof siteTreeHints != 'undefined') && siteTreeHints[ RegExp.$1 ]) {
this.hints = siteTreeHints[ RegExp.$1 ];
this.dropperOptions = {
accept : (this.hints.allowedChildren && (this.className.indexOf('nochildren') == -1))
? this.hints.allowedChildren : 'none'
};
}
}
if(this.className.indexOf('current') > -1) {
if(!this.tree.selected) this.tree.selected = [];
this.tree.selected.push(this);
}
if(!this.hints) this.hints = {}
},
destroy: function () {
if(this.TreeNode) this.TreeNode.destroy();
this.TreeNode = null;
this.TreeNodeAPI = null;
},
castAsTree: function(childUL) {
behaveAs(childUL, SiteSubTree, this.options);
if(this.draggableObj) childUL.makeDraggable();
},
onselect: function() {
this.selectTreeNode();
return false;
},
/**
* Drag'n'drop handlers - Ajax saving
*/
onParentChanged : function(node, oldParent, newParent) {
if(newParent.id.match(/^record-new/)) {
alert("You must save the page before dragging children into it");
return false;
}
if( node == newParent || node.getIdx() == newParent.getIdx() ) {
alert("You cannot add a page to itself");
return false;
}
if(node.innerHTML.toLowerCase().indexOf('<del') > -1) {
alert("You can't moved deleted pages");
return false;
}
if( Element.hasClassName( newParent, 'nochildren' ) ) {
alert("You can't add children to that node");
return false;
}
var currentlyOpenPageID = 0;
if($('Form_EditForm').elements.ID) currentlyOpenPageID = $('Form_EditForm').elements.ID.value;
statusMessage('saving...', '', true);
new Ajax.Request(SiteTreeHandlers.parentChanged_url, {
method : 'post',
postBody : 'ID=' + node.getIdx() + '&ParentID=' + newParent.getIdx() + '&CurrentlyOpenPageID=' + currentlyOpenPageID,
onSuccess : Ajax.Evaluator,
onFailure : function(response) {
errorMessage('error saving parent', response);
}
});
return true;
},
/**
* Called when the tree has been resorted
* nodeList is a list of all the nodes in the correct rder
* movedNode is the node that actually got moved to trigger this resorting
*/
onOrderChanged : function(nodeList, movedNode) {
statusMessage('saving...', '', true);
var i, parts = Array();
sort = 0;
for(i=0;i<nodeList.length;i++) {
if(nodeList[i].getIdx && nodeList[i].getIdx()) {
parts[parts.length] = 'ID[]=' + nodeList[i].getIdx();
// Ensure that the order of new records is preserved when they are moved THEN saved
if( nodeList[i].id.indexOf("record-new") == 0 )
if( $('Form_EditForm_ID') && ( 'record-' + $('Form_EditForm_ID').value == nodeList[i].id ) )
if( $('Form_EditForm_Sort') )
$('Form_EditForm_Sort').value = ++sort;
}
}
if(movedNode.getIdx && movedNode.getIdx()) {
parts[parts.length] = 'MovedNodeID=' + movedNode.getIdx();
}
var currentlyOpenPageID = 0;
if($('Form_EditForm').elements.ID) currentlyOpenPageID = $('Form_EditForm').elements.ID.value;
if(parts) {
new Ajax.Request(SiteTreeHandlers.orderChanged_url, {
method : 'post',
postBody : parts.join('&') + '&CurrentlyOpenPageID=' + currentlyOpenPageID,
/*onSuccess : function(response) {
// statusMessage(response.responseText, 'good');
},*/
onSuccess: Ajax.Evaluator,
onFailure : function(response) {
errorMessage('error saving order', response);
}
});
}
return true;
}
}
/**
* Action panel at the top of the left hand tree
*/
treeactions = {
toggleSelection : function(li) {
li = (li.tagName.toLowerCase() == 'a') ? li.parentNode : li;
// Deselect
if(li.selected) this.closeSelection(li, true);
else this.openSelection(li, true);
fixHeight_left();
return li.selected;
},
closeSelection : function(li, nested) {
li = (li.tagName.toLowerCase() == 'a') ? li.parentNode : li;
Element.hide(_HANDLER_FORMS[li.id]);
removeClass(li, 'selected');
li.selected = false;
$(_HANDLER_FORMS[li.id]).notify('Close');
if(li.onclose) li.onclose();
if(typeof nested == 'undefined') fixHeight_left();
},
openSelection: function(li, nested) {
li = (li.tagName.toLowerCase() == 'a') ? li.parentNode : li;
addClass(li, 'selected');
Element.show(_HANDLER_FORMS[li.id]);
li.selected = true;
// Deselect siblings
var i, set = li.parentNode.getElementsByTagName('li');
for(i=0;i<set.length;i++) if(li != set[i] && set[i].selected) {
this.closeSelection(set[i], true);
}
if(typeof nested == 'undefined') fixHeight_left();
}
}
function fixHeight_left() {
if($('treepanes')) fitToParent('treepanes');
var st = $('SideTabs');
if(st) st.resize();
else fitToParent('sitetree_holder');
}
// Build the site tree
SiteTree.applyTo('#sitetree');
/**
* Reorganise action
*/
ReorganiseAction = Class.create();
ReorganiseAction.applyTo('#sortitems');
ReorganiseAction.prototype = {
initialize: function () {
this.getElementsByTagName('a')[0].onclick = returnFalse;
Observable.applyTo($(_HANDLER_FORMS[this.id]));
},
onclick : function() {
if(treeactions.toggleSelection(this)) {
$('sitetree').makeDraggable();
}
},
onclose : function() {
$('sitetree').stopBeingDraggable();
}
}
/**
* Control the site tree filter
*/
SiteTreeFilterForm = Class.create();
SiteTreeFilterForm.applyTo('form#SiteTreeFilter');
SiteTreeFilterForm.prototype = {
initialize: function() {
this.valueField = $('SiteTreeFilterValue');
this.submitButton = $('SiteTreeFilterButton');
this.submitButton.onclick = this.submitclick.bind(this);
// this.submitButton.onclick = function() { alert('Click'); return false; }
},
submitclick: function() {
new Ajax.Request( this.action + '?SiteTreeFilterValue=' + encodeURIComponent( this.valueField.value ) + '&ajax=1', {
onSuccess: function( response ) {
alert(response.responseText);
$('sitetree').innerHTML = response.responseText;
Behaviour.apply( $('sitetree') );
statusMessage('Filtered tree','good');
},
onFailure: function( response ) {
errorMessage('Could not filter site tree<br />' + response.responseText);
}
});
return false;
}
}

463
javascript/LeftAndMain_right.js Executable file
View File

@ -0,0 +1,463 @@
CMSForm = Class.extend('ChangeTracker').extend('Observable');
CMSForm.prototype = {
initialize : function(fn) {
this.ChangeTracker.initialize();
this.formName = fn;
this.prepareForm();
},
/**
* Processing called whenever a page is loaded in the right - including the initial one
*/
prepareForm : function() {
ajaxActionsAtTop(this.id, 'form_actions_' + this.formName, this.formName);
},
/**
* Load actions from a string containing the HTML content
*/
loadActionsFromString : function(actionHTML) {
var actionHolder = $('form_actions_' + this.formName);
actionHolder.innerHTML = actionHTML;
prepareAjaxActions(actionHolder, this.id, this.formName);
},
/**
* Close the form down without any corrective action, after its been deleted.
*/
closeIfSetTo: function(id) {
if(this.elements.ID && this.elements.ID.value == id) {
this.innerHTML = "<p>This page was deleted. To edit a page, select it from the left.</p>";
if(typeof tinyMCE != 'undefined') tinyMCE.instances = [];
}
},
/**
* Reload lose the form if the current page is open.
*/
reloadIfSetTo: function(id) {
if(this.elements.ID && this.elements.ID.value == id) {
this.getPageFromServer(id);
}
},
close: function() {
this.innerHTML = "<p>&#160;</p>";
var actions;
if(actions = $('form_actions_' + this.formName)) {
actions.parentNode.removeChild(actions);
}
},
updateStatus: function( newStatus ) {
if( $('Form_EditForm_Status') )
$('Form_EditForm_Status').innerHTML = "STATUS: " + newStatus;
},
/**
* Load a new page into the right-hand form.
*
* @param formContent string
* @param response object (optional)
* @param evalResponse boolean (optional)
*/
loadNewPage : function(formContent, response, evalResponse) {
//alert('here: ' + formContent);
var rightHTML = formContent;
// Rewrite # links
rightHTML = rightHTML.replace(/href *= *"#/g, 'href="' + window.location.href.replace(/#.*$/,'') + '#');
// Rewrite iframe links (for IE)
rightHTML = rightHTML.replace(/(<iframe[^>]*src=")([^"]+)("[^>]*>)/g, '$1' + baseHref() + '$2$3');
// Prepare iframes for removal, otherwise we get loading bugs
var i, allIframes = this.getElementsByTagName('iframe');
if(allIframes) for(i=0;i<allIframes.length;i++) {
allIframes[i].contentWindow.location.href = 'about:blank';
allIframes[i].parentNode.removeChild(allIframes[i]);
}
if(response && evalResponse) {
Ajax.Evaluator(response);
} else {
this.innerHTML = rightHTML;
}
// Get the form attributes from embedded fields
var attr;
for(attr in {'action':true ,'method':true,'enctype':true,'name':true}) {
if(this.elements['_form_' + attr]) {
this[attr] = this.elements['_form_' + attr].value;
this.elements['_form_' + attr].parentNode.removeChild(this.elements['_form_' + attr]);
}
}
allIframes = this.getElementsByTagName('iframe');
if(allIframes) for(i=0;i<allIframes.length;i++) {
try {
allIframes[i].contentWindow.location.href = allIframes[i].src;
} catch(er) {alert(er.message);}
}
_TAB_DIVS_ON_PAGE = [];
try {
var tabs = document.getElementsBySelector('#' + this.id + ' ul.tabstrip');
} catch(er) { /*alert('a: '+ er.message + '\n' + er.line);*/ }
try {
for(var i=0;i<tabs.length;i++) if(tabs[i].tagName) initTabstrip(tabs[i]);
} catch(er) { /*alert('b: '+ er.message + '\n' + er.line);*/ }
if((typeof tinyMCE != 'undefined') && tinyMCE.instances) {
tinyMCE.instances = [];
tinyMCE.isLoaded = false;
tinyMCE.onLoad();
}
if(this.prepareForm) this.prepareForm();
Behaviour.apply(this);
if(this.resetElements) this.resetElements();
window.ontabschanged();
},
/**
* Save the contens of the form, by submitting it and resetting is changed checker
* on success.
*/
save: function(ifChanged, callAfter, action) {
_AJAX_LOADING = true;
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
if(!action) action = "save";
var __callAfter = callAfter;
var __form = this;
if(__form.notify) __form.notify('BeforeSave', __form.elements.ID.value);
// validate if required
if(this.validate && !this.validate()) {
// TODO Automatically switch to the tab/position of the first error
statusMessage("Validation failed.", "bad");
return false;
}
var success = function(response) {
Ajax.Evaluator(response);
__form.resetElements();
if(__callAfter) __callAfter();
if(__form.notify) __form.notify('PageSaved', __form.elements.ID.value);
_AJAX_LOADING = false;
}
if(ifChanged) {
var data = this.serializeChangedFields('ID') + '&ajax=1&action_' + action + '=1';
} else {
var data = this.serializeAllFields() + '&ajax=1&action_' + action + '=1';
}
statusMessage("Saving...", null, true);
new Ajax.Request(this.action, {
method : this.method,
postBody: data,
onSuccess : success,
onFailure : function(response) {
errorMessage('Error saving content', response);
_AJAX_LOADING = false;
}
});
},
loadPage_url : 'admin/getpage'
}
CMSRightForm = Class.extend('CMSForm');
CMSRightForm.prototype = {
intialize: function() {
this.CMSForm.initialize('right');
},
/**
* Load the given URL (with &ajax=1) into this form
*/
loadURLFromServer : function(url) {
var urlParts = url.match( /ID=(\d+)/ );
var id = urlParts ? urlParts[1] : null;
if( !url.match( /^https?:\/\/.*/ ) )
url = document.getElementsByTagName('base')[0].href + url;
new Ajax.Request( url + '&ajax=1', {
asynchronous : true,
onSuccess : function( response ) {
$('Form_EditForm').successfullyReceivedPage(response,id);
},
onFailure : function(response) {
alert(response.responseText);
errorMessage('error loading page',response);
}
});
},
successfullyReceivedPage : function(response,pageID) {
var loadingNode = $('sitetree').loadingNode;
if( loadingNode && pageID && parseInt( $('sitetree').getIdxOf( loadingNode ) ) != pageID ) {
return;
}
// must wait until the javascript has finished
document.body.style.cursor = 'wait';
this.loadNewPage(response.responseText);
var subform;
if(subform = $('Form_MemberForm')) subform.close();
if(subform = $('Form_SubForm')) subform.close();
if(this.elements.ID) {
this.notify('PageLoaded', this.elements.ID.value);
}
if(this.receivingID) {
// Treenode might not exist if that part of the tree is closed
var treeNode = loadingNode ? loadingNode : $('sitetree').getTreeNodeByIdx(this.receivingID);
if(treeNode) {
$('sitetree').setCurrentByIdx(treeNode.getIdx());
treeNode.removeNodeClass('loading');
}
statusMessage('');
}
// must wait until the javascript has finished
document.body.style.cursor = 'default';
},
didntReceivePage : function(response) {
errorMessage('error loading page', response);
$('sitetree').getTreeNodeByIdx(this.elements.ID.value).removeNodeClass('loading');
},
/**
* Request a page from the server via Ajax
*/
getPageFromServer : function(id, treeNode) {
// if(id && id.match(/^[A-Za-z0-9_]+$/)) {
if(id && (id == 'root' || parseInt(id) == id || (id.substr && id.substr(0,3) == 'new') )) {
this.receivingID = id;
// Treenode might not exist if that part of the tree is closed
if(!treeNode) treeNode = $('sitetree').getTreeNodeByIdx(id);
if(treeNode) {
$('sitetree').loadingNode = treeNode;
treeNode.addNodeClass('loading');
url = treeNode.aTag.href + (treeNode.aTag.href.indexOf('?')==-1?'?':'&') + 'ajax=1';
}
if(SiteTreeHandlers.loadPage_url) {
var sep = (SiteTreeHandlers.loadPage_url.indexOf('?') == -1) ? '?' : '&';
url = SiteTreeHandlers.loadPage_url + sep + 'ID=' + id;
}
statusMessage("loading...");
this.loadURLFromServer(url);
} else {
throw("getPageFromServer: Bad page ID: " + id);
}
},
/**
* Set the status field
*/
setStatus: function(newStatus) {
var statusLabel = document.getElementsBySelector('label.pageStatusMessage')[0];
if(statusLabel) statusLabel.innerHTML = "STATUS: " + newStatus;
}
}
CMSForm.applyTo('#Form_SubForm', 'rightbottom');
CMSRightForm.applyTo('#Form_EditForm', 'right');
function action_save_right() {
_AJAX_LOADING = true;
$('Form_EditForm').save(false);
}
ActionPropertiesForm = Class.create();
ActionPropertiesForm.prototype = {
/**
* Open the form
*/
open : function() {
var allInputs = this.getElementsByTagName('input');
this.submitButton = allInputs[allInputs.length-1];
this.submitButton.onclick = this.send.bind(this);
this.style.display = '';
// This detects whether we've opened a new page
if($('Form_EditForm').elements.ID.value != this.elements.ID.value) {
this.elements.ID.value = $('Form_EditForm').elements.ID.value;
new Ajax.Updater(
'action_submit_options_recipient',
'admin/getpagemembers/' + this.elements.ID.value + '?SecurityLevel=' + this.securityLevel
);
}
},
close : function() {
this.style.display = 'none';
},
toggle : function() {
if(this.style.display == '') this.close();
else this.open();
},
/**
* Submit the option form and carry out the action
*/
send : function() {
// Show a "submitting..." box
if(!this.sendingText) {
this.sendingText = document.createElement('div');
this.sendingText.innerHTML = 'Submitting...';
this.sendingText.className = 'sendingText';
Element.setOpacity(this.sendingText, 0.9);
this.appendChild(this.sendingText);
}
this.sendingText.style.display = '';
// Send the request
var buttons = document.getElementsBySelector('#' + this.id + ' p.Actions input');
var actionButton = null;
if( buttons )
actionButton = buttons[0];
ajaxSubmitForm(false, this.onComplete.bind(this), this, actionButton ? actionButton.name : '', 'submit');
return false;
},
/**
* Process the action's Ajax response
*/
onComplete: function() {
this.sendingText.style.display = 'none';
if(this.elements.Message) this.elements.Message.value = '';
this.close();
$('Form_EditForm').getPageFromServer($('Form_EditForm_ID').value);
}
}
/**
* Handle auto-saving. Detects if changes have been made, and if so save everything on the page.
* If confirmation is true it will ask for confirmation.
*/
function autoSave(confirmation, callAfter) {
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
var __forms = []
if($('Form_EditForm')) __forms.push($('Form_EditForm'));
if($('Form_SubForm')) __forms.push($('Form_SubForm'));
if($('Form_MemberForm')) __forms.push($('Form_MemberForm'));
var __somethingHasChanged = false;
var __callAfter = callAfter;
__forms.each(function(form) {
if(form.isChanged && form.isChanged()) {
__somethingHasChanged = true;
}
});
if(__somethingHasChanged) {
var options = {
save: function() {
statusMessage('saving...', '', true);
var i;
for(i=0;i<__forms.length;i++) {
if(__forms[i].isChanged && __forms[i].isChanged()) {
if(i == 0) __forms[i].save(true, __callAfter);
else __forms[i].save(true);
}
}
},
discard: function() {
__forms.each(function(form) { form.resetElements(false); });
if(__callAfter) __callAfter();
},
cancel: function() {
}
}
/**
* Fix the modal dialog!
*/
if(confirmation ) doYouWantToSave(options);
else options.save();
} else {
if(__callAfter) __callAfter();
}
}
StageLink = Class.create();
StageLink.prototype = {
initialize: function(getVars, urlField) {
this.getVars = getVars;
this.urlField = urlField;
var boundNewPage = this.newPage.bind(this);
$('Form_EditForm').observeMethod('PageLoaded', boundNewPage);
$('Form_EditForm').observeMethod('PageSaved', boundNewPage);
$('Form_EditForm').observeMethod('PagePublished', boundNewPage);
$('Form_EditForm').observeMethod('PageUnpublished', boundNewPage);
this.newPage();
},
newPage : function() {
var linkField = $('Form_EditForm').elements[this.urlField];
var linkVal = linkField ? linkField.value : null;
if(linkVal) {
if(this.id != 'viewArchivedSite') this.style.display = '';
this.href = this.baseURL() + linkVal + '/' + this.getVars;
} else {
if(this.id != 'viewArchivedSite') this.style.display = 'none';
}
},
onclick : function() {
var w = window.open(this.href, windowName('site'));
w.focus();
return false;
},
baseURL : function() {
return document.getElementsByTagName('base')[0].href;
}
}
StageLink.applyTo('#viewStageSite', '?stage=Stage', 'StageURLSegment');
StageLink.applyTo('#viewLiveSite', '?stage=Live', 'LiveURLSegment');
StageLink.applyTo('#viewArchivedSite', '', 'URLSegment');
window.name = windowName('cms');
/**
* Return a unique window name that contains the URL
*/
function windowName(suffix) {
var base = document.getElementsByTagName('base')[0].href.replace('http://','').replace(/\//g,'_').replace(/\./g,'_');
return base + suffix;
}

322
javascript/MemberList.js Executable file
View File

@ -0,0 +1,322 @@
/**
* Auto-lookup on ajax fields
*/
AjaxMemberLookup = {
initialise : function() {
var div = document.createElement('div');
div.id = this.id + '_ac';
div.className = 'autocomplete';
this.parentNode.appendChild(div);
new Ajax.Autocompleter(this.id, div.id, 'admin/security/autocomplete/' + this.name, {
afterUpdateElement : this.afterAutocomplete.bind(this)
});
},
afterAutocomplete : function(field, selectedItem) {
var data = selectedItem.getElementsByTagName('span')[1].innerHTML;
var items = data.split(",");
form = Element.ancestorOfType(field, 'form');
form.elements.FirstName.value = items[0];
form.elements.Surname.value = items[1];
form.elements.Email.value = items[2];
if(items[3])
form.elements.Password.value = items[3];
var fieldSet = field.parentNode.parentNode.getElementsByTagName('input');
ajaxSubmitFieldSet('admin/security/savemember?MemberListBaseGroupID='.$('MemberListBaseGroupID'), fieldSet);
}
}
/**
* Member list behaviour
*/
Behaviour.register({
'#MemberList tr' : {
onmouseover : hover_over,
onmouseout : hover_out,
onclick : function() {
if(this.className.indexOf('addrow') == -1) {
Element.addClassName(this, 'loading');
new Ajax.Request('admin/security/getmember', {
method : 'post',
postBody : 'ID=' + this.id.replace('member-','') + '&ajax=1',
onSuccess : this.select_success.bind(this)
});
} else {
if($('Form_MemberForm'))
$('Form_MemberForm').innerHTML = "<p>Choose a member from above.</p>";
}
},
select_success : function(response) {
Element.removeClassName(this, 'loading');
$('Form_MemberForm').loadNewPage(response.responseText);
statusMessage('loaded','good');
// for (var n in tinyMCE.instances) tinyMCE.removeMCEControl(n);
}
},
'#MemberList thead tr' : {
onmouseover : null,
onmouseout : null,
onclick : null
},
'#MemberList' : {
initialise : function() {
this.headerMap = [];
var i, item, headers = this.getElementsByTagName('thead')[0].getElementsByTagName('tr')[0].getElementsByTagName('td');
for(i=0;item=headers[i];i++) {
this.headerMap[i] = item.className;
}
},
setRecordDetails : function(id, details, groupID) {
var row = document.getElementById('member-' + id);
if(row) {
var i, item, cells = row.getElementsByTagName('td');
for(i=0;item=cells[i];i++) {
if(details[this.headerMap[i]]) {
item.innerHTML = details[this.headerMap[i]];
}
}
} else {
this.createRecord(id, details, groupID);
}
},
createRecord : function (id, details, groupId) {
var row = document.createElement('tr');
row.id = 'member-' + id;
var i, cell, cellField;
for(i=0;cellField=this.headerMap[i];i++) {
cell = document.createElement('td')
if(details[cellField]) {
cell.innerHTML = details[cellField];
}
row.appendChild(cell);
}
// Add the delete icon
if(typeof groupId == 'undefined')
var groupId = $('Form_EditForm').elements.ID.value;
cell = document.createElement('td')
cell.innerHTML = '<a class="deletelink" href="admin/security/removememberfromgroup/' + groupId + '/' + id + '"><img src="cms/images/delete.gif" alt="delete" /></a>';
cell.getElementsByTagName('0');
row.appendChild(cell);
var tbody = this.getElementsByTagName('tbody')[0];
var addRow = document.getElementsByClassName('addrow',tbody)[0];
if(addRow) tbody.insertBefore(row, addRow);
else tbody.appendChild(row);
Behaviour.apply(row, true);
},
clearAddForm : function() {
var tbody = this.getElementsByTagName('tbody')[0];
var addRow = document.getElementsByClassName('addrow',tbody)[0];
if(addRow) {
var i,field,fields = addRow.getElementsByTagName('input');
for(i=0;field=fields[i];i++) {
if(field.type != 'hidden' && field.type != 'submit') field.value = '';
}
}
},
removeMember : function(memberID) {
var record;
if(record = $('member-' + memberID)) {
record.parentNode.removeChild(record);
}
}
},
'#MemberList input' : AjaxMemberLookup,
'#MemberList a.deletelink' : {
onclick : function(event) {
if(confirm("Do you want to remove this member from the group?")) {
this.getElementsByTagName('img')[0].src = 'cms/images/network-save.gif';
ajaxLink(this.href);
}
Event.stop(event);
return false;
}
},
'#MemberList tr.addrow' : {
onmouseover : null,
onmouseout : null,
onclick : null
},
'#MemberList tr.addrow td.actions input' : {
initialise: function() {
data = this.parentNode.parentNode.getElementsByTagName('input');
var i,item,error = [];
for(i=0;item=data[i];i++) {
item.originalSerialized = Form.Element.serialize(item);
}
},
onclick : function(event) {
data = this.parentNode.parentNode.getElementsByTagName('input');
var i,item,error = [];
for(i=0;item=data[i];i++) {
if(item.name == 'Email' && !item.value) error[error.length] = "Email";
if(item.name == 'Password' && !item.value) error[error.length] = "Password";
}
if(error.length > 0) {
alert('Please enter a ' + error.join(' and a ') + ' to add a member.');
} else {
ajaxSubmitFieldSet('admin/security/addmember?MemberListBaseGroupID='.$('MemberListBaseGroupID') , data);
}
return false;
}
},
'#Form_EditForm' : {
changeDetection_fieldsToIgnore : {
'MemberListBaseGroup' : true,
'MemberListOrderByField' : true,
'MemberListOrderByOrder' : true,
'MemberListGroup' : true,
'MemberListSearch' : true
}
}
});
MemberListFilterButton = Class.create();
MemberListFilterButton.applyTo('div.MemberListField #MemberListFilterButton');
MemberListFilterButton.prototype = {
initialize: function() {
this.inputFields = new Array();
var childNodes = this.parentNode.getElementsByTagName('input');
for( var index = 0; index < childNodes.length; index++ ) {
if( childNodes[index].tagName ) {
childNodes[index].resetChanged = function() { return false; }
childNodes[index].isChanged = function() { return false; }
this.inputFields.push( childNodes[index] );
}
}
childNodes = this.parentNode.getElementsByTagName('select');
for( var index = 0; index < childNodes.length; index++ ) {
if( childNodes[index].tagName ) {
childNodes[index].resetChanged = function() { return false; }
childNodes[index].field_changed = function() { return false; }
this.inputFields.push( childNodes[index] );
}
}
},
isChanged: function() {
return false;
},
onclick: function(event) {
var baseGroup = $('MemberListBaseGroupID').value;
var updateURL = 'admin/security/listmembers/' + baseGroup + '?';
for( var index = 0; index < this.inputFields.length; index++ ) {
if( this.inputFields[index].tagName ) {
updateURL += this.inputFields[index].name + '=' + encodeURIComponent( this.inputFields[index].value ) + '&';
}
}
updateURL += 'ajax=1';
new Ajax.Request( updateURL, {
onSuccess: function( response ) {
$('MemberList').innerHTML = response.responseText;
// Behaviour.debug();
Behaviour.apply( $('MemberList') );
},
onFailure: function( response ) {
errorMessage('Could not filter results: ' + response.responseText );
}
});
return false;
}
}
Behaviour.register({
'div#MemberList div.PageControls a' : {
onclick: function() {
new Ajax.Request( this.href + '&ajax=1', {
onSuccess: function( response ) {
$('MemberList').innerHTML = response.responseText;
// Behaviour.debug();
Behaviour.apply( $('MemberList') );
},
onFailure: function( response ) {
errorMessage('Could not filter results: ' + response.responseText );
}
});
return false;
}
}
});
function reloadMemberList( groupID ) {
if( !groupID )
groupID = $('MemberListBaseGroupID').value;
if($('MemberListStart')) var listStart = $('MemberListStart').value;
else var listStart = 0;
new Ajax.Request( 'admin/security/listmembers?&ajax=1&MemberListBaseGroup=' + groupID + '&MemberListStart=' + listStart, {
onSuccess: function( response ) {
$('MemberList').innerHTML = response.responseText;
// Behaviour.debug();
Behaviour.apply( $('MemberList') );
},
onFailure: function( response ) {
errorMessage('Could not filter results: ' + response.responseText );
}
});
}
Behaviour.register({
'.MemberListFilter a.showhide' : {
initialise: function() {
this.open = !Element.hasClassName( this, 'closed' );
},
onclick: function() {
if( this.open )
this.openControls();
else
this.closeControls();
this.open = !this.open;
return false;
},
openControls: function() {
Element.removeClassName( this, 'closed' );
$('MemberListFilterControls').style.display = 'block';
},
closeControls: function() {
Element.removeClassName( this, 'closed' );
$('MemberListFilterControls').style.display = 'none';
}
}
});

298
javascript/MemberTableField.js Executable file
View File

@ -0,0 +1,298 @@
/**
* Modified 2006-10-05, Ingo Schommer
* This is more or less a copy of Member.js, with additions and changes
* to match the switch from Member.php to MemberTableField.php all over the UI.
* Eventually it will replace Member.js (please remove this message then).
*/
// no confirm message for removal from a group
ComplexTableField.prototype.deleteConfirmMessage = null;
/**
* Auto-lookup on ajax fields
*/
AjaxMemberLookup = {
initialise : function() {
var div = document.createElement('div');
div.id = this.id + '_ac';
div.className = 'autocomplete';
this.parentNode.appendChild(div);
if(this.id) {
new Ajax.Autocompleter(this.id, div.id, 'admin/security/autocomplete/' + this.name, {
afterUpdateElement : this.afterAutocomplete.bind(this)
});
}
},
afterAutocomplete : function(field, selectedItem) {
var data = selectedItem.getElementsByTagName('span')[1].innerHTML;
var items = data.split(",");
form = Element.ancestorOfType(field, 'form');
// TODO more flexible column-detection
form.elements.FirstName.value = items[0];
form.elements.Surname.value = items[1];
form.elements.Email.value = items[2];
if(items[3] && form.elements.Password)
form.elements.Password.value = items[3];
//var fieldSet = field.parentNode.parentNode.getElementsByTagName('input');
//ajaxSubmitFieldSet('admin/security/savemember?MemberBaseGroupID='.$('MemberBaseGroupID'), fieldSet);
}
}
MemberTableField = Class.create();
MemberTableField.applyTo('#Form_EditForm div.MemberTableField');
MemberTableField.prototype = {
initialize: function() {
Behaviour.register({
'#Form_EditForm div.MemberFilter input' : {
onkeypress : this.prepareSearch.bind(this)
},
'#Form_EditForm div.MemberTableField table.data tr.addtogrouprow input' : {
onkeypress : this.prepareAddToGroup.bind(this)
},
'#Form_EditForm div.MemberTableField table.data tr.addtogrouprow #Form_AddRecordForm_action_addtogroup' : {
onclick : this.prepareAddToGroup.bind(this)
},
'#Form_EditForm div.MemberTableField table.data tr.addtogrouprow td.actions input' : {
initialise: function() {
data = this.parentNode.parentNode.getElementsByTagName('input');
var i,item,error = [];
for(i=0;item=data[i];i++) {
item.originalSerialized = Form.Element.serialize(item);
}
},
onclick : this.addToGroup.bind(this)
},
//'#Form_EditForm div.MemberTableField input' : AjaxMemberLookup,
'#Form_EditForm' : {
changeDetection_fieldsToIgnore : {
'ctf[start]' : true,
'ctf[ID]' : true,
'MemberOrderByField' : true,
'MemberOrderByOrder' : true,
'MemberGroup' : true,
'MemberFilterButton' : true,
'MemberFieldName' : true,
'MemberDontShowPassword' : true,
'Surname' : true,
'FirstName' : true,
'Email' : true,
'MemberSearch' : true
}
}
});
},
// prevent submission of wrong form-button (MemberFilterButton)
prepareAddToGroup: function(e) {
// IE6 doesnt send an event-object with onkeypress
var event = (e) ? e : window.event;
var keyCode = (event.keyCode) ? event.keyCode : event.which;
if(keyCode == Event.KEY_RETURN) {
var el = Event.element(event);
this.addToGroup(event);
Event.stop(event);
return false;
}
},
// prevent submission of wrong form-button (MemberFilterButton)
prepareSearch: function(e) {
// IE6 doesnt send an event-object with onkeypress
var event = (e) ? e : window.event;
var keyCode = (event.keyCode) ? event.keyCode : event.which;
if(keyCode == Event.KEY_RETURN) {
var el = Event.element(event);
$('MemberFilterButton').onclick(event);
Event.stop(event);
return false;
}
},
addToGroup: function(e) {
// only submit parts of the form
var data = this.parentNode.parentNode.getElementsByTagName('input');
var i,item,error = [];
var form = Event.findElement(e,"form");
for(i=0;item=data[i];i++) {
if(item.name == 'Email' && !item.value) error[error.length] = "Email";
if(item.name == 'Password' && !item.value) error[error.length] = "Password";
}
if(error.length > 0) {
alert('Please enter a ' + error.join(' and a ') + ' to add a member.');
} else {
updateURL = "";
updateURL += Event.findElement(e,"form").action;
// we can't set "fieldName" as a HiddenField because there might be multiple ComplexTableFields in a single EditForm-container
updateURL += "&fieldName="+$('MemberFieldName').value;
updateURL += "&action_callfieldmethod&&methodName=addtogroup&";
ajaxSubmitFieldSet(updateURL, data);
}
return false;
}
/*
initialise : function() {
this.headerMap = [];
var i, item, headers = this.getElementsByTagName('thead')[0].getElementsByTagName('tr')[0].getElementsByTagName('td');
for(i=0;item=headers[i];i++) {
this.headerMap[i] = item.className;
}
},
setRecordDetails : function(id, details, groupID) {
var row = document.getElementById('member-' + id);
if(row) {
var i, item, cells = row.getElementsByTagName('td');
for(i=0;item=cells[i];i++) {
if(details[this.headerMap[i]]) {
item.innerHTML = details[this.headerMap[i]];
}
}
} else {
this.createRecord(id, details, groupID);
}
},
createRecord : function (id, details, groupId) {
var row = document.createElement('tr');
row.id = 'member-' + id;
var i, cell, cellField;
for(i=0;cellField=this.headerMap[i];i++) {
cell = document.createElement('td')
if(details[cellField]) {
cell.innerHTML = details[cellField];
}
row.appendChild(cell);
}
// Add the delete icon
if(typeof groupId == 'undefined')
var groupId = $('Form_EditForm').elements.ID.value;
cell = document.createElement('td')
cell.innerHTML = '<a class="deletelink" href="admin/security/removememberfromgroup/' + groupId + '/' + id + '"><img src="cms/images/delete.gif" alt="delete" /></a>';
cell.getElementsByTagName('0');
row.appendChild(cell);
var tbody = this.getElementsByTagName('tbody')[0];
var addRow = document.getElementsByClassName('addrow',tbody)[0];
if(addRow) tbody.insertBefore(row, addRow);
else tbody.appendChild(row);
Behaviour.apply(row, true);
},
clearAddForm : function() {
var tbody = this.getElementsByTagName('tbody')[0];
var addRow = document.getElementsByClassName('addrow',tbody)[0];
if(addRow) {
var i,field,fields = addRow.getElementsByTagName('input');
for(i=0;field=fields[i];i++) {
if(field.type != 'hidden' && field.type != 'submit') field.value = '';
}
}
},
removeMember : function(memberID) {
var record;
if(record = $('member-' + memberID)) {
record.parentNode.removeChild(record);
}
}
*/
}
MemberFilterButton = Class.create();
MemberFilterButton.applyTo('#Form_EditForm #MemberFilterButton');
MemberFilterButton.prototype = {
initialize: function() {
this.inputFields = new Array();
var childNodes = this.parentNode.getElementsByTagName('input');
for( var index = 0; index < childNodes.length; index++ ) {
if( childNodes[index].tagName ) {
childNodes[index].resetChanged = function() { return false; }
childNodes[index].isChanged = function() { return false; }
this.inputFields.push( childNodes[index] );
}
}
childNodes = this.parentNode.getElementsByTagName('select');
for( var index = 0; index < childNodes.length; index++ ) {
if( childNodes[index].tagName ) {
childNodes[index].resetChanged = function() { return false; }
childNodes[index].field_changed = function() { return false; }
this.inputFields.push( childNodes[index] );
}
}
},
isChanged: function() {
return false;
},
onclick: function(e) {
if(!$('ctf-ID') || !$('MemberFieldName')) {
return false;
}
var updateURL = "";
updateURL += Event.findElement(e,"form").action;
// we can't set "fieldName" as a HiddenField because there might be multiple ComplexTableFields in a single EditForm-container
updateURL += "&fieldName="+$('MemberFieldName').value;
updateURL += "&action_callfieldmethod&&methodName=ajax_refresh&";
for( var index = 0; index < this.inputFields.length; index++ ) {
if( this.inputFields[index].tagName ) {
updateURL += this.inputFields[index].name + '=' + encodeURIComponent( this.inputFields[index].value ) + '&';
}
}
updateURL += 'ajax=1';
new Ajax.Request( updateURL, {
onSuccess: Ajax.Evaluator,
onFailure: function( response ) {
errorMessage('Could not filter results: ' + response.responseText );
}.bind(this)
});
return false;
}
}
// has to be external from initialize() because otherwise request will double on each reload - WTF
Behaviour.register({
'#Form_EditForm div.MemberTableField table.data input' : AjaxMemberLookup
});
/*
function reloadMemberTableField( groupID ) {
if( !groupID )
groupID = $('MemberBaseGroupID').value;
if($('MemberStart')) var listStart = $('MemberStart').value;
else var listStart = 0;
new Ajax.Request( 'admin/security/listmembers?&ajax=1&MemberBaseGroup=' + groupID + '&MemberStart=' + listStart, {
onSuccess: function( response ) {
$('MemberList').innerHTML = response.responseText;
// Behaviour.debug();
Behaviour.apply( $('MemberList') );
},
onFailure: function( response ) {
errorMessage('Could not filter results: ' + response.responseText );
}
});
}
*/

View File

@ -0,0 +1,42 @@
MemberTableFieldPopupForm = Class.extend("ComplexTableFieldPopupForm");
MemberTableFieldPopupForm.prototype = {
initialize: function() {
this.ComplexTableFieldPopupForm.initialize();
Behaviour.register({
"form#MemberTableField_Popup_DetailForm input.action": {
onclick: this.submitForm.bind(this)
},
'form#MemberTableField_Popup_DetailForm input' : {
initialise : function() {
if(this.name == 'FirstName' || this.name == 'Surname' || this.name == 'Email') {
var div = document.createElement('div');
div.id = this.id + '_ac';
div.className = 'autocomplete';
this.parentNode.appendChild(div);
/*
new Ajax.Autocompleter(this.id, div.id, 'admin/security/autocomplete/' + this.name, {
afterUpdateElement : this.afterAutocomplete.bind(this)
});
*/
}
},
afterAutocomplete : function(field, selectedItem) {
var data = selectedItem.getElementsByTagName('span')[1].innerHTML;
var items = data.split(",");
this.elements.FirstName.value = items[0];
this.elements.Surname.value = items[1];
this.elements.Email.value = items[2];
this.elements.Password.value = items[3];
}
}
//'form#MemberTableField_Popup_DetailForm input' : AjaxMemberLookup
});
},
}
MemberTableFieldPopupForm.applyTo('form#MemberTableField_Popup_DetailForm');

View File

@ -0,0 +1,404 @@
if(typeof SiteTreeHandlers == 'undefined') SiteTreeHandlers = {};
SiteTreeHandlers.showNLDraft_url = 'admin/newsletter/shownewsletter/';
SiteTreeHandlers.showNLType_url = 'admin/newsletter/showtype/';
SiteTree.prototype = {
castAsTreeNode: function(li) {
behaveAs(li, SiteTreeNode, this.options);
},
getIdxOf : function(treeNode) {
if(treeNode && treeNode.id)
return treeNode.id;
},
getTreeNodeByIdx : function(idx) {
if(!idx) idx = "0";
return document.getElementById(idx);
},
initialise: function() {
this.observeMethod('SelectionChanged', this.changeCurrentTo);
},
createTreeNode : function(idx, title, pageType, secondIdx) {
var i;
var node = document.createElement('li');
node.className = pageType;
var aTag = document.createElement('a');
node.id = idx;
if( pageType == 'Draft' ) {
aTag.href = SiteTreeHandlers.showNLDraft_url + idx;
} else {
aTag.href = SiteTreeHandlers.showNLType_url + idx;
}
aTag.innerHTML = title;
node.appendChild(aTag);
SiteTreeNode.create(node, this.options);
return node;
},
addDraftNode: function( title, parentID, draftID ) {
var draftNode = this.createTreeNode( 'draft_' + parentID + '_' + draftID, title, 'Draft', parentID );
this.getTreeNodeByIdx('drafts_' + parentID).appendTreeNode( draftNode );
this.changeCurrentTo( draftNode );
},
addTypeNode: function( title, parentID ) {
var typeNode = this.createTreeNode( 'mailtype_' + parentID, title, 'MailType' );
var draftsNode = this.createTreeNode( 'drafts_' + parentID, 'Drafts', 'DraftFolder' );
var sentItemsNode = this.createTreeNode( 'sent_' + parentID, 'Sent Items', 'SentFolder' );
var mailingListNode = this.createTreeNode( 'recipients_' + parentID, 'Mailing List', 'Recipients' );
typeNode.appendTreeNode( draftsNode );
Element.addClassName( draftsNode, 'nodelete');
typeNode.appendTreeNode( sentItemsNode );
Element.addClassName(sentItemsNode,'nodelete');
typeNode.appendTreeNode( mailingListNode );
Element.addClassName(mailingListNode,'nodelete');
this.appendTreeNode( typeNode );
this.changeCurrentTo( typeNode );
}
}
/**
* Delete pages action
*/
deletedraft = {
button_onclick : function() {
if(treeactions.toggleSelection(this)) {
deletedraft.o1 = $('sitetree').observeMethod('SelectionChanged', deletedraft.treeSelectionChanged);
deletedraft.o2 = $('deletedrafts_options').observeMethod('Close', deletedraft.popupClosed);
addClass($('sitetree'),'multiselect');
var sel = $('sitetree').firstSelected();
if(sel) {
var selIdx = $('sitetree').getIdxOf(sel);
deletedraft.selectedNodes = { };
deletedraft.selectedNodes[selIdx] = true;
sel.removeNodeClass('current');
sel.addNodeClass('selected');
}
}
return false;
},
treeSelectionChanged : function(selectedNode) {
var idx = $('sitetree').getIdxOf(selectedNode);
if(selectedNode.selected) {
selectedNode.removeNodeClass('selected');
selectedNode.selected = false;
deletedraft.selectedNodes[idx] = false;
} else {
selectedNode.addNodeClass('selected');
selectedNode.selected = true;
deletedraft.selectedNodes[idx] = true;
}
return false;
},
popupClosed : function() {
removeClass($('sitetree'),'multiselect');
$('sitetree').stopObserving(deletedraft.o1);
$('deletedrafts_options').stopObserving(deletedraft.o2);
for(var idx in deletedraft.selectedNodes) {
if(deletedraft.selectedNodes[idx]) {
node = $('sitetree').getTreeNodeByIdx(idx);
if(node) {
node.removeNodeClass('selected');
node.selected = false;
}
}
}
},
form_submit : function() {
var csvIDs = "";
for(var idx in deletedraft.selectedNodes) {
if(deletedraft.selectedNodes[idx]) csvIDs += (csvIDs ? "," : "") + idx;
}
if(csvIDs) {
$('deletedrafts_options').elements.csvIDs.value = csvIDs;
Ajax.SubmitForm('deletedrafts_options', null, {
onSuccess : function(response) {
Ajax.Evaluator(response);
var sel;
if((sel = $('sitetree').selected) && sel.parentNode) sel.addNodeClass('current');
// else $('Form_EditForm').innerHTML = "";
treeactions.closeSelection($('deletedrafts'));
},
onFailure : function(response) {
errorMessage('Error deleting drafts', response);
}
});
$('deletedrafts').getElementsByTagName('a')[0].onclick();
} else {
alert("Please select at least 1 page.");
}
return false;
},
treeSelectionChanged : function(selectedNode) {
var idx = $('sitetree').getIdxOf(selectedNode);
if( !deletedraft.selectedNodes )
deletedraft.selectedNodes = {};
if(selectedNode.className.indexOf('nodelete') == -1) {
if(selectedNode.selected) {
selectedNode.removeNodeClass('selected');
selectedNode.selected = false;
deletedraft.selectedNodes[idx] = false;
} else {
selectedNode.addNodeClass('selected');
selectedNode.selected = true;
deletedraft.selectedNodes[idx] = true;
}
}
return false;
}
}
SiteTreeNode.prototype.onselect = function() {
$('sitetree').changeCurrentTo(this);
if($('sitetree').notify('SelectionChanged', this)) {
if(typeof save != 'undefined'){
autoSave(true, this.getPageFromServer.bind(this));
}
else {
this.getPageFromServer();
}
}
return false;
};
SiteTreeNode.prototype.getPageFromServer = function() {
var match = this.id.match(/(mailtype|drafts|draft|sent|recipients)_([\d]+)$/);
var openTabName = null;
var currentPageID = null;
if( $('Form_EditForm_ID') )
currentPageID = $('Form_EditForm_ID').value;
var newPageID = null;
var otherID = null;
var type = null;
var forceReload = $('Form_EditForm_Type') && $('Form_EditForm_Type').value == 'Newsletter';
if( match ) {
// open the appropriate tab
switch( match[1] ) {
case 'recipients':
openTabName = 'Root_Recipients_set_Recipients';
break;
case 'drafts':
openTabName = 'Root_Drafts';
break;
case 'sent':
openTabName = 'Root_Sent_set_Sent';
break;
}
newPageID = match[2];
type = match[1];
} else if(this.id.match(/(mailtype|drafts|draft|sent|recipients)_([\d]+)_([\d]+)$/)) {
newPageID = RegExp.$2;
type = RegExp.$1;
otherID = RegExp.$3;
forceReload = true;
}
if( forceReload || currentPageID != newPageID )
$('Form_EditForm').getPageFromServer(newPageID, type, otherID, openTabName);
else
openTab( openTabName );
};
function draft_sent_ok( newsletterID, draftID ) {
var draftsListNode = $('drafts_' + newsletterID);
var sentListNode = $('sent_' + newsletterID);
var draftNode = $('draft_' + newsletterID + '_' + draftID );
draftsListNode.removeTreeNode( draftNode );
draftNode.id = 'sent_' + newsletterID + '_' + draftID;
sentListNode.appendTreeNode( draftNode, null );
statusMessage('Sent email to mailing list', 'good');
}
function resent_ok( newsletterID, sentID ) {
statusMessage('Resent email to mailing list', 'good');
}
function reloadSiteTree() {
new Ajax.Request( 'admin/newsletter/getsitetree', {
method: get,
onSuccess: function( response ) {
$('sitetree_holder').innerHTML = response.responseText;
},
onFailure: function( response ) {
}
});
}
_HANDLER_FORMS['addtype'] = 'addtype_options';
_HANDLER_FORMS['deletedrafts'] = 'deletedrafts_options';
AddForm = Class.extend('addpage');
AddForm.applyTo('#addtype');
AddForm.prototype = {
initialize: function () {
Observable.applyTo($(_HANDLER_FORMS[this.id]));
this.getElementsByTagName('a')[0].onclick = returnFalse;
$(_HANDLER_FORMS[this.id]).onsubmit = this.form_submit;
},
form_submit : function() {
var st = $('sitetree');
if( st.selected.length )
selectedNode = st.selected[0];
else
selectedNode = st.selected;
var parentID = null;
while( selectedNode && !parentID ) {
if( selectedNode && selectedNode.id && selectedNode.id.match(/mailtype_([0-9a-z\-]+)$/) )
parentID = RegExp.$1;
else
selectedNode = selectedNode.parentNode;
}
if(parentID && parentID.substr(0,3) == 'new') {
alert("You have to save a page before adding children underneath it");
} else {
this.elements.ParentID.value = parentID;
var type = 'draft';
var selectIDPrefix = 'draft_' + parentID + '_';
if( $('add_type').value == 'type' ) {
type = 'type';
selectIDPrefix = 'mailtype_';
}
var request = new Ajax.Request( this.action + '?ajax=1&PageType=' + type + '&ParentID=' + parentID, {
method: 'get',
asynchronous: true,
onSuccess : function( response ) {
$('Form_EditForm').loadNewPage(response.responseText);
// create a new node and add it to the site tree
if( type == 'draft' ) {
$('sitetree').addDraftNode('New draft newsletter', parentID, $('Form_EditForm_ID').value );
} else {
$('sitetree').addTypeNode('New newsletter type', $('Form_EditForm_ID').value );
}
statusMessage('Added new ' + type);
},
onFailure : function(response) {
alert(response.responseText);
statusMessage('Could not add new ' + type );
}
});
}
return false;
},
reloadSiteTree: function( response ) {
statusMessage('Added new newsletter type', 'good' );
$('sitetree_holder').innerHTML = response.responseText;
Behaviour.apply( $('sitetree_holder') );
},
button_onclick : function() {
if(treeactions.toggleSelection(this)) {
var selectedNode = $('sitetree').firstSelected();
if(selectedNode) {
while(selectedNode.parentTreeNode && !selectedNode.hints.defaultChild) {
$('sitetree').changeCurrentTo(selectedNode.parentTreeNode);
selectedNode = selectedNode.parentTreeNode;
}
}
this.o1 = $('sitetree').observeMethod('SelectionChanged', this.treeSelectionChanged.bind(this));
this.o2 = $(_HANDLER_FORMS[this.id]).observeMethod('Close', this.popupClosed.bind(this));
}
return false;
},
treeSelectionChanged: function( treeNode ) {
this.selected = treeNode;
},
popupClosed: function() {
$('sitetree').stopObserving(this.o1);
$(_HANDLER_FORMS.addtype).stopObserving(this.o2);
}
}
function removeTreeNodeByIdx( tree, nodeID ) {
var node = tree.getTreeNodeByIdx( nodeID );
if( node )
if( node.parentTreeNode )
node.parentTreeNode.removeTreeNode( node );
else
$('sitetree').removeTreeNode( node );
}
/**
* Initialisation function to set everything up
*/
Behaviour.addLoader(function () {
// Set up add draft
Observable.applyTo($('addtype_options'));
if( $('addtype') ) {
if( AddForm.button_click )
$('addtype').getElementsByTagName('a')[0].onclick = function() {return false;};
if( AddForm.button_click )
$('addtype_options').onsubmit = AddForm.form_submit;
}
// Set up delete drafts
Observable.applyTo($('deletedrafts_options'));
var deleteDrafts = $('deletedrafts');
if( deleteDrafts ) {
deleteDrafts.onclick = deletedraft.button_onclick;
deleteDrafts.getElementsByTagName('a')[0].onclick = function() {return false;};
$('deletedrafts_options').onsubmit = deletedraft.form_submit;
}
});

View File

@ -0,0 +1,536 @@
function action_send_right() {
$('action_send_options').toggle();
}
CMSForm.applyTo('#Form_MemberForm','rightbottom');
Behaviour.register({
'#Form_EditForm' : {
initialise : function() {
this.openTab = null;
this.prepareForm();
},
/**
* Processing called whenever a page is loaded in the right - including the initial one
*/
prepareForm : function() {
ajaxActionsAtTop('Form_EditForm', 'form_actions', 'right');
},
/**
* Request a page from the server via Ajax
*/
getPageFromServer : function(id, type, otherid,openTabName) {
this.openTab = openTabName;
if(id && parseInt(id) == id) {
this.receivingID = id;
// Treenode might not exist if that part of the tree is closed
var treeNode = $('sitetree').getTreeNodeByIdx(id);
if(treeNode) treeNode.addNodeClass('loading');
statusMessage("loading...");
var requestURL = 'admin/newsletter/showtype/' + id;
if( otherid )
requestURL = 'admin/newsletter/shownewsletter/' + otherid;
new Ajax.Request(requestURL, {
asynchronous : true,
method : 'post',
postBody : /*'ID=' + id + 'type=' + type + */'ajax=1'+ (otherid?('&otherid='+otherid):''),
onSuccess : this.successfullyReceivedPage.bind(this),
onFailure : function(response) {
errorMessage('error loading page',response);
}
});
} else {
throw("getPageFromServer: Bad page ID: " + id);
}
},
successfullyReceivedPage : function(response) {
this.loadNewPage(response.responseText);
// Treenode might not exist if that part of the tree is closed
var treeNode = $('sitetree').getTreeNodeByIdx(this.receivingID);
if(treeNode) {
$('sitetree').changeCurrentTo(treeNode);
treeNode.removeNodeClass('loading');
}
statusMessage('');
onload_init_tabstrip();
if( this.openTab ) {
openTab( this.openTab );
this.openTab = null;
}
},
didntReceivePage : function(response) {
errorMessage('error loading page', response);
$('sitetree').getTreeNodeByIdx(this.elements.ID.value).removeNodeClass('loading');
},
/**
* Load a new page into the right-hand form
*/
loadNewPage : function(formContent) {
rightHTML = formContent;
rightHTML = rightHTML.replace(/href *= *"#/g, 'href="' + window.location.href.replace(/#.*$/,'') + '#');
// Prepare iframes for removal, otherwise we get loading bugs
var i, allIframes = this.getElementsByTagName('iframe');
if(allIframes) for(i=0;i<allIframes.length;i++) {
allIframes[i].contentWindow.location.href = 'about:blank';
allIframes[i].parentNode.removeChild(allIframes[i]);
}
this.innerHTML = rightHTML;
allIframes = this.getElementsByTagName('iframe');
if(allIframes) for(i=0;i<allIframes.length;i++) {
try {
allIframes[i].contentWindow.location.href = allIframes[i].src;
} catch(er) {alert(er.message);}
}
_TAB_DIVS_ON_PAGE = [];
try {
var tabs = document.getElementsBySelector('#Form_EditForm ul.tabstrip');
} catch(er) {/* alert('a: '+ er.message + '\n' + er.line);*/ }
try {
for(var i=0;i<tabs.length;i++) if(tabs[i].tagName) initTabstrip(tabs[i]);
} catch(er) { /*alert('b: '+ er.message + '\n' + er.line); */}
if((typeof tinyMCE != 'undefined') && tinyMCE.instances) {
tinyMCE.instances = [];
tinyMCE.isLoaded = false;
tinyMCE.onLoad();
}
onload_init_tabstrip();
// if(this.prepareForm) this.prepareForm();
Behaviour.apply($('Form_EditForm'));
if(this.prepareForm)
this.prepareForm();
this.resetElements();
window.ontabschanged();
},
/**
* Save the contens of the form, by submitting it and resetting is changed checker
* on success.
*/
save: function(ifChanged, callAfter) {
_AJAX_LOADING = true;
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
var __callAfter = callAfter;
var __form = this;
if(__form.notify) __form.notify('BeforeSave', __form.elements.ID.value);
var success = function(response) {
Ajax.Evaluator(response);
__form.resetElements();
if(__callAfter) __callAfter();
if(__form.notify) __form.notify('PageSaved', __form.elements.ID.value);
_AJAX_LOADING = false;
}
var action = 'action_save';
if( $('Form_NewsletterEditForm_Type') && $('Form_NewsletterEditForm_Type').value == 'Newsletter' )
action += 'newsletter';
if(ifChanged) {
var data = this.serializeChangedFields('ID') + '&ajax=1&' + action + '=1';
} else {
var data = this.serializeAllFields() + '&ajax=1&' + action + '=1';
}
// alert(this.action + '\n\n' + data);
new Ajax.Request(this.action, {
method : this.method,
postBody: data,
onSuccess : success,
onFailure : function(response) {
alert(response.responseText);
errorMessage('Error saving content', response);
_AJAX_LOADING = false;
}
});
}
}
});
Behaviour.register({
'#action_send_options' : {
/**
* Open the form
*/
open : function() {
this.elements.NewsletterID.value = $('Form_EditForm').elements.ID.value;
var allInputs = this.getElementsByTagName('input');
this.submitButton = allInputs[allInputs.length-1];
this.submitButton.onclick = this.send.bind(this);
this.style.display = '';
},
close : function() {
this.style.display = 'none';
},
toggle : function() {
if(this.style.display == '') this.close();
else this.open();
},
send_test: function() {
// Show a "submitting..." box
if(!this.sendingText) {
this.sendingText = document.createElement('div');
this.sendingText.innerHTML = 'Sending newsletter...';
this.sendingText.className = 'sendingText';
Element.setOpacity(this.sendingText, 0.9);
this.appendChild(this.sendingText);
}
this.sendingText.style.display = '';
var form = $('Form_EditForm');
if( form.isChanged() )
form.save();
// Send the request
ajaxSubmitForm(false, this.onCompleteTest.bind(this), this, '', 'sendnewsletter')
return false;
},
send: function() {
if( $('SendTypeList').checked )
return this.send_to_list();
else
return this.send_test();
},
/**
* Submit the option form and carry out the action
*/
send_to_list : function() {
// Show a "submitting..." box
/*if(!this.sendingText) {
this.sendingText = document.createElement('div');
this.sendingText.innerHTML = 'Sending newsletter...';
this.sendingText.className = 'sendingText';
Element.setOpacity(this.sendingText, 0.9);
this.appendChild(this.sendingText);
}
this.sendingText.style.display = '';*/
var form = $('Form_EditForm');
if( form.isChanged() )
form.save();
if( $('SendProgressBar') )
$('SendProgressBar').start();
// Send the request
Ajax.SubmitForm( $('Form_EditForm'), 'action_sendnewsletter', {
extraData: '&SendType=List',
onSuccess: this.incrementProcess.bind(this),
onFailure: function(response) {
statusMessage(response.responseText);
}
});
/*var form = $('Form_EditForm');
var data = form.serializeChangedFields('ID','type') + '&ajax=1&action_savenewsletter=1&SendType=List';
new Ajax.Request(form.action, {
method : form.method,
postBody: data,
onSuccess : this.incrementProcess.bind(this),
onFailure : function(response) {
errorMessage('Error sending to mailing list', response);
}
});*/
return false;
},
incrementProcess: function( response ) {
var processParts = response.responseText.match( /(\d+):(\d+)\/(\d+)/ );
if( !processParts || parseInt( processParts[2] ) >= parseInt( processParts[3] ) ) {
this.onComplete( response );
return;
}
// update the progress bar
$('SendProgressBar').setProgress( ( parseInt( processParts[2] ) / parseInt( processParts[3] ) ) * 100 );
var estimate = $('SendProgressBar').estimateTime();
$('SendProgressBar').setText( 'Sent ' + processParts[2] + ' of ' + processParts[3] + '. ' + estimate + ' remaining...' );
// set the action to the batch process controller
var updateRequest = baseHref() + 'processes/next/' + processParts[1] + '/10?ajax=1';
var request = new Ajax.Request( updateRequest, {
onSuccess: this.incrementProcess.bind(this),
onFailure: function( response ) {
errorMessage( response.responseText );
}
});
},
/**
* Process the action's Ajax response
*/
onComplete: function( response ) {
// $('SendProgressBar').setProgress( 100 );
// $('SendProgressBar').setText( 'Done!' );
$('SendProgressBar').reset();
// this.elements.Message.value = '';
this.close();
if( response )
Ajax.Evaluator(response);
},
onCompleteTest: function( response ) {
// this.sendingText.innerHTML = '';
if( this.sendingText.parentNode == this )
this.removeChild( this.sendingText );
this.close();
if( response )
Ajax.Evaluator(response);
}
}
});
function save(ifChanged, callAfter, confirmation) {
tinyMCE.triggerSave(true);
var form = $('Form_EditForm');
var alreadySaved = false;
if(ifChanged) {
if(form.elements.length < 2) alreadySaved = true;
else alreadySaved = !form.isChanged();
}
if(alreadySaved && ifChanged) { // pressing save always saves
if(!ifChanged && !confirmation) {
statusMessage("There's nothing to save");
}
if(callAfter) callAfter();
} else {
var options = {
save: (function() {
statusMessage('saving...', '', true);
var success = function(response) {
Ajax.Evaluator(response);
if(this.callAfter) this.callAfter();
}
if(callAfter) success = success.bind(this);
if( $('Form_EditForm_Type').value == 'Newsletter' )
var data = form.serializeChangedFields('ID','type') + '&ajax=1&action_savenewsletter=1';
else
var data = form.serializeChangedFields('ID','type') + '&ajax=1&action_save=1';
new Ajax.Request(form.action, {
method : form.method,
postBody: data,
onSuccess : success,
onFailure : function(response) {
errorMessage('Error saving content', response);
}
});
}).bind({callAfter : callAfter}),
discard: callAfter,
cancel: function() {
}
}
if(confirmation) doYouWantToSave(options);
else options.save();
}
return false;
}
Behaviour.register({
/**
* When the iframe has loaded, apply the listeners
*/
'div#ImportFile' : {
frameLoaded: function( doc ) {
this.showTable = true;
var fileField = doc.getElementsByTagName('input')[0];
var idField = doc.getElementsByTagName('input')[1];
idField.value = $('Form_EditForm_ID').value;
fileField.onchange = this.selectedFile.bind(this);
},
loadTable: function( doc ) {
this.innerHTML = doc.getElementsByTagName('body')[0].innerHTML;
}
}
});
NewsletterList = Class.create();
NewsletterList.applyTo('table.NewsletterList');
NewsletterList.prototype = {
initialize: function() {
this.tableBody = this.getElementsByTagName('tbody')[0];
this.deleteLinks = this.getElementsByTagName('a');
for( var i = 0; i < this.deleteLinks.length; i++ ) {
this.deleteLinks[i].onclick = this.deleteNewsletter.bindAsEventListener(this);
}
},
deleteNewsletter: function( event ) {
var link = event.target;
if( event.srcElement )
link = event.srcElement;
var rowNode = link.parentNode.parentNode;
new Ajax.Request( link.href, {
onSuccess: this.removeRow,
onFailure: function( response ) {
alert('The newsletter could not be deleted');
}
});
},
removeRow: function( response ) {
this.tableBody.removeChild( $(response.responseText) );
}
}
/**
* Add page action
* @todo Remove duplication between this and the CMSMain Add page action
*/
Behaviour.register( {
'#Form_EditForm_action_save': {
onclick : function() {
$('Form_EditForm').save();
return false;
}
}
});
/**
* Handle auto-saving. Detects if changes have been made, and if so save everything on the page.
* If confirmation is true it will ask for confirmation.
*/
function autoSave(confirmation, callAfter) {
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
var __forms = []
if($('Form_EditForm')) __forms.push($('Form_EditForm'));
if($('Form_SubForm')) __forms.push($('Form_SubForm'));
if($('Form_MemberForm')) __forms.push($('Form_MemberForm'));
var __somethingHasChanged = false;
var __callAfter = callAfter;
__forms.each(function(form) {
if(form.isChanged && form.isChanged()) {
__somethingHasChanged = true;
}
});
if(__somethingHasChanged) {
var options = {
save: function() {
statusMessage('saving...', '', true);
var i;
for(i=0;i<__forms.length;i++) {
if(__forms[i].isChanged && __forms[i].isChanged()) {
if(i == 0) __forms[i].save(true, __callAfter);
else __forms[i].save(true);
}
}
},
discard: function() {
__forms.each(function(form) { form.resetElements(false); });
if(__callAfter) __callAfter();
},
cancel: function() {
}
}
if(confirmation) doYouWantToSave(options);
else options.save();
} else {
if(__callAfter) __callAfter();
}
}
function reloadRecipientsList() {
var id = $('Form_EditForm_ID').value;
var request = new Ajax.Request( 'admin/newsletter/getrecipientslist/' + id + '?ajax=1', {
onSuccess: function( response ) {
$('MemberList').outerHTML = response.responseText;
Behaviour.apply( $('MemberList') );
},
onFailure: function(response) {
statusMessage('Could not automatically refresh recipients list', 'bad');
}
});
}
/*RecipientImportField = Class.create();
RecipientImportField.applyTo('iframe.RecipientImportField');
RecipientImportField.prototype = {
initialize: function() {
this.src = document.getElementsByTagName('base')[0].href + this.src;
}
}*/
/**
* We don't want hitting the enter key in the subject field
* to submit the form.
*/
Behaviour.register({
'#Form_EditForm_Subject' : {
onkeypress : function(event) {
event = (event) ? event : window.event;
var kc = event.keyCode ? event.keyCode : event.charCode;
if(kc == 13) {
return false;
}
}
}
});

View File

@ -0,0 +1,172 @@
/**
* Auto-lookup on ajax fields
*/
AjaxMemberLookup = {
initialise : function() {
var div = document.createElement('div');
div.id = this.id + '_ac';
div.className = 'autocomplete';
this.parentNode.appendChild(div);
new Ajax.Autocompleter(this.id, div.id, 'admin/security/autocomplete/' + this.name, {
afterUpdateElement : this.afterAutocomplete.bind(this)
});
},
afterAutocomplete : function(field, selectedItem) {
var data = selectedItem.getElementsByTagName('span')[1].innerHTML;
var items = data.split(",");
form = Element.ancestorOfType(field, 'form');
form.elements.FirstName.value = items[0];
form.elements.Surname.value = items[1];
form.elements.Email.value = items[2];
form.elements.Password.value = items[3];
var fieldSet = field.parentNode.parentNode.getElementsByTagName('input');
ajaxSubmitFieldSet('admin/newsletter/savemember', fieldSet);
}
}
/**
* Member list behaviour
*/
Behaviour.register({
'#MemberList tr' : {
onmouseover : hover_over,
onmouseout : hover_out,
onclick : function() {
if(this.className.indexOf('addrow') == -1) {
Element.addClassName(this, 'loading');
new Ajax.Request('admin/newsletter/getmember', {
method : 'post',
postBody : 'ID=' + this.id.replace('member-','') + '&ajax=1',
onSuccess : this.select_success.bind(this)
});
} else {
$('Form_MemberForm').innerHTML = "<p>Choose a member from above.</p>";
}
},
select_success : function(response) {
Element.removeClassName(this, 'loading');
$('Form_MemberForm').loadNewPage(response.responseText);
statusMessage('loaded','good');
// for (var n in tinyMCE.instances) tinyMCE.removeMCEControl(n);
}
},
'#MemberList thead tr' : {
onmouseover : null,
onmouseout : null,
onclick : null
},
'#MemberList' : {
initialise : function() {
this.headerMap = [];
var i, item, headers = this.getElementsByTagName('thead')[0].getElementsByTagName('tr')[0].getElementsByTagName('td');
for(i=0;item=headers[i];i++) {
this.headerMap[i] = item.className;
}
},
setRecordDetails : function(id, details, groupID) {
var row = document.getElementById('member-' + id);
if(row) {
var i, item, cells = row.getElementsByTagName('td');
for(i=0;item=cells[i];i++) {
if(details[this.headerMap[i]]) {
item.innerHTML = details[this.headerMap[i]];
}
}
} else {
this.createRecord(id, details, groupID);
}
},
createRecord : function (id, details, groupId) {
var row = document.createElement('tr');
row.id = 'member-' + id;
var i, cell, cellField;
for(i=0;cellField=this.headerMap[i];i++) {
cell = document.createElement('td')
if(details[cellField]) {
cell.innerHTML = details[cellField];
}
row.appendChild(cell);
}
// Add the delete icon
if(typeof groupId == 'undefined')
var groupId = $('Form_EditForm').elements.ID.value;
cell = document.createElement('td')
cell.innerHTML = '<a class="deletelink" href="admin/security/removememberfromgroup/' + groupId + '/' + id + '"><img src="cms/images/delete.gif" alt="delete" /></a>';
cell.getElementsByTagName('0');
row.appendChild(cell);
var tbody = this.getElementsByTagName('tbody')[0];
var addRow = document.getElementsByClassName('addrow',tbody)[0];
if(addRow) tbody.insertBefore(row, addRow);
else tbody.appendChild(row);
Behaviour.apply(row, true);
},
clearAddForm : function() {
var tbody = this.getElementsByTagName('tbody')[0];
var addRow = document.getElementsByClassName('addrow',tbody)[0];
if(addRow) {
var i,field,fields = addRow.getElementsByTagName('input');
for(i=0;field=fields[i];i++) {
if(field.type != 'hidden' && field.type != 'submit') field.value = '';
}
}
},
removeMember : function(memberID) {
var record;
if(record = $('member-' + memberID)) {
record.parentNode.removeChild(record);
}
}
},
'#MemberList input' : AjaxMemberLookup,
'#MemberList a.deletelink' : {
onclick : function(event) {
if(confirm("Do you want to remove this member from the group?")) {
this.getElementsByTagName('img')[0].src = 'cms/images/network-save.gif';
ajaxLink(this.href);
}
Event.stop(event);
return false;
}
},
'#MemberList tr.addrow' : {
onmouseover : null,
onmouseout : null,
onclick : null
},
'#MemberList tr.addrow td.actions input' : {
onclick : function(event) {
data = this.parentNode.parentNode.getElementsByTagName('input');
var i,item,error = [];
for(i=0;item=data[i];i++) {
if(item.name == 'Email' && !item.value) error[error.length] = "Email";
if(item.name == 'Password' && !item.value) error[error.length] = "Password";
}
if(error.length > 0) {
alert('Please enter a ' + error.join(' and a ') + ' to add a member.');
} else {
ajaxSubmitFieldSet('admin/newsletter/addmember', data);
}
return false;
}
}
});

View File

@ -0,0 +1,13 @@
Behaviour.register({
'body' : {
onload: function() {
top.getElementById('ImportFile').frameLoaded( document );
}
},
'form input[type=file]' : {
onchange: function() {
this.form.submit();
}
}
});

View File

@ -0,0 +1,186 @@
/**
* Ajax to support the comment posting system
*/
PageCommentInterface = Class.create();
PageCommentInterface.prototype = {
initialize: function() {
Behaviour.register({
'#PageCommentInterface_Form_PageCommentsPostCommentForm_action_postcomment' : {
onclick : this.postComment
},
'#PageComments a.deletelink' : {
onclick : this.deleteComment
},
'#PageComments a.spamlink' : {
onclick : this.reportSpam
},
'#PageComments a.hamlink' : {
onclick : this.reportHam
}
});
},
postComment: function() {
var form = $("PageCommentInterface_Form_PageCommentsPostCommentForm");
var message = $("PageCommentInterface_Form_PageCommentsPostCommentForm_error");
if(form.elements.Name.value && form.elements.Comment.value) {
if(noComments = $('NoComments')) {
Element.remove(noComments);
var pageComments = document.createElement('ul');
pageComments.id = 'PageComments';
$('CommentHolder').appendChild(pageComments);
}
message.style.display = 'none';
// Create a new <li> for the post
var pageComments = $('PageComments').getElementsByTagName('li');
var __newComment = document.createElement('li');
// Add it to the list with a 'loading' message
$('PageComments').insertBefore(__newComment, pageComments[0]);
__newComment.innerHTML = '<p><img src="cms/images/network-save.gif" /> Loading...</p>';
// Submit the form via ajax
Ajax.SubmitForm(form, "action_postcomment", {
onSuccess : function(response) {
// Load the response into the new <li>
__newComment.innerHTML = response.responseText;
Behaviour.apply(__newComment);
// Flash it using Scriptaculous
new Effect.Highlight(__newComment, { endcolor: '#e9e9e9' } );
if(response.responseText.match('<b>Spam detected!!</b>')) {
__newComment.className = 'spam';
}
},
onFailure : function(response) {
alert(response.responseText);
}
});
// Clear the fields
// form.elements.Name.value = "";
form.elements.Comment.value = "";
} else {
message.style.display = '';
message.innerHTML = "Please enter your name and a comment to be posted to the site.";
}
return false;
},
/**
* Ajax handler of moderation removal
*/
deleteComment: function() {
var __comment = this.parentNode.parentNode.parentNode;
__comment.getElementsByTagName('span')[0].innerHTML = "Removing...";
new Ajax.Request(this.href + '?ajax=1', {
onSuccess : function(response) {
// Clear our wee status message
__comment.getElementsByTagName('span')[0].innerHTML = "Removing...";
// Remove it using Scriptaculous
new Effect.Highlight(__comment, {
startcolor: '#cc9999' , endcolor: '#e9e9e9', duration: 0.5,
afterFinish : function () {
var commentList = __comment.parentNode;
commentList.removeChild(__comment);
if(!commentList.firstChild) {
$('CommentHolder').innerHTML = "<p id=\"NoComments\">No one has commented on this page yet.</p>";
}
}
} );
},
onFailure : function(response) {
alert(response.responseText);
}
});
return false;
},
/**
* Ajax handler of spam reporting
*/
reportSpam: function() {
var __comment = this.parentNode.parentNode.parentNode;
__comment.getElementsByTagName('span')[0].innerHTML = "Reporting spam...";
new Ajax.Request(this.href + '?ajax=1', {
onSuccess : function(response) {
if(response.responseText != '') {
// Load the response into the <li>
__comment.innerHTML = response.responseText;
Behaviour.apply(__comment);
// Flash it using Scriptaculous
new Effect.Highlight(__comment, { endcolor: '#cc9999' } );
__comment.className = 'spam';
} else {
new Effect.Highlight(__comment, {
startcolor: '#cc9999' , endcolor: '#e9e9e9', duration: 0.5,
afterFinish : function() {
var commentList = __comment.parentNode;
commentList.removeChild(__comment);
if(!commentList.firstChild) {
$('CommentHolder').innerHTML = "<p id=\"NoComments\">No one has commented on this page yet.</p>";
}
}
} );
}
},
onFailure : function(response) {
alert(response.responseText);
}
});
return false;
},
/**
* Ajax handler of ham reporting
*/
reportHam: function() {
var __comment = this.parentNode.parentNode.parentNode;
__comment.getElementsByTagName('span')[0].innerHTML = "Reporting as not spam...";
new Ajax.Request(this.href + '?ajax=1', {
onSuccess : function(response) {
// Load the response into the <li>
__comment.innerHTML = response.responseText;
Behaviour.apply(__comment);
// Flash it using Scriptaculous
new Effect.Highlight(__comment, { endcolor: '#e9e9e9' } );
__comment.className = 'notspam';
},
onFailure : function(response) {
alert(response.responseText);
}
});
return false;
}
}
PageCommentInterface.applyTo("#PageComments_holder");

54
javascript/ReportAdmin_left.js Executable file
View File

@ -0,0 +1,54 @@
if(typeof SiteTreeHandlers == 'undefined') SiteTreeHandlers = {};
SiteTree.prototype = {
castAsTreeNode: function(li) {
behaveAs(li, SiteTreeNode, this.options);
},
getIdxOf : function(treeNode) {
if(treeNode && treeNode.id)
return treeNode.id;
},
getTreeNodeByIdx : function(idx) {
if(!idx) idx = "0";
return document.getElementById(idx);
},
initialise: function() {
this.observeMethod('SelectionChanged', this.changeCurrentTo);
}
};
SiteTreeNode.prototype.onselect = function() {
$('sitetree').changeCurrentTo(this);
if($('sitetree').notify('SelectionChanged', this)) {
this.getPageFromServer();
}
return false;
};
SiteTreeNode.prototype.getPageFromServer = function() {
if(this.id)
$('Form_EditForm').getPageFromServer(this.id);
};
function reloadSiteTree() {
new Ajax.Request( 'admin/report/getsitetree', {
method: get,
onSuccess: function( response ) {
$('sitetree_holder').innerHTML = response.responseText;
},
onFailure: function( response ) {
}
});
}
appendLoader(function () {
if(typeof($('sitetree').selected) == 'undefined');
var selectedNode = $('sitetree').getElementsByTagName('li')[1];
selectedNode.onselect();
});

118
javascript/ReportAdmin_right.js Executable file
View File

@ -0,0 +1,118 @@
Behaviour.register({
'#Form_EditForm' : {
initialise : function() {
this.openTab = null;
this.prepareForm();
},
/**
* Processing called whenever a page is loaded in the right - including the initial one
*/
prepareForm : function() {
ajaxActionsAtTop('Form_EditForm', 'form_actions', 'right');
},
/**
* Request a page from the server via Ajax
*/
getPageFromServer : function(id) {
if(id) {
this.receivingID = id;
// Treenode might not exist if that part of the tree is closed
var treeNode = $('sitetree').getTreeNodeByIdx(id);
if(treeNode) treeNode.addNodeClass('loading');
statusMessage("loading...");
var requestURL = 'admin/reports/showreport/' + id;
new Ajax.Request(requestURL, {
asynchronous : true,
method : 'post',
postBody : 'ajax=1',
onSuccess : this.successfullyReceivedPage.bind(this),
onFailure : function(response) {
errorMessage('error loading page',response);
}
});
} else {
throw("getPageFromServer: Bad page ID: " + id);
}
},
successfullyReceivedPage : function(response) {
this.loadNewPage(response.responseText);
// Treenode might not exist if that part of the tree is closed
var treeNode = $('sitetree').getTreeNodeByIdx(this.receivingID);
if(treeNode) {
$('sitetree').changeCurrentTo(treeNode);
treeNode.removeNodeClass('loading');
}
statusMessage('');
onload_init_tabstrip();
if( this.openTab ) {
openTab( this.openTab );
this.openTab = null;
}
},
didntReceivePage : function(response) {
errorMessage('error loading page', response);
$('sitetree').getTreeNodeByIdx(this.elements.ID.value).removeNodeClass('loading');
},
/**
* Load a new page into the right-hand form
*/
loadNewPage : function(formContent) {
rightHTML = formContent;
rightHTML = rightHTML.replace(/href *= *"#/g, 'href="' + window.location.href.replace(/#.*$/,'') + '#');
// Prepare iframes for removal, otherwise we get loading bugs
var i, allIframes = this.getElementsByTagName('iframe');
if(allIframes) for(i=0;i<allIframes.length;i++) {
allIframes[i].contentWindow.location.href = 'about:blank';
allIframes[i].parentNode.removeChild(allIframes[i]);
}
this.innerHTML = rightHTML;
allIframes = this.getElementsByTagName('iframe');
if(allIframes) for(i=0;i<allIframes.length;i++) {
try {
allIframes[i].contentWindow.location.href = allIframes[i].src;
} catch(er) {alert(er.message);}
}
_TAB_DIVS_ON_PAGE = [];
try {
var tabs = document.getElementsBySelector('#Form_EditForm ul.tabstrip');
} catch(er) {/* alert('a: '+ er.message + '\n' + er.line);*/ }
try {
for(var i=0;i<tabs.length;i++) if(tabs[i].tagName) initTabstrip(tabs[i]);
} catch(er) { /*alert('b: '+ er.message + '\n' + er.line); */}
if((typeof tinyMCE != 'undefined') && tinyMCE.instances) {
tinyMCE.instances = [];
tinyMCE.isLoaded = false;
tinyMCE.onLoad();
}
// if(this.prepareForm) this.prepareForm();
Behaviour.apply($('Form_EditForm'));
if(this.prepareForm)
this.prepareForm();
this.resetElements();
window.ontabschanged();
}
}
});

147
javascript/SecurityAdmin_left.js Executable file
View File

@ -0,0 +1,147 @@
SiteTreeHandlers.parentChanged_url = 'admin/security/ajaxupdateparent';
SiteTreeHandlers.orderChanged_url = 'admin/security/ajaxupdatesort';
SiteTreeHandlers.loadPage_url = 'admin/security/getitem';
_HANDLER_FORMS['deletegroup'] = 'deletegroup_options';
/**
* Add page action
* @todo Remove duplication between this and the CMSMain Add page action
*/
addgroup = {
button_onclick : function() {
addgroup.form_submit();
return false;
},
form_submit : function() {
var st = $('sitetree');
$('addgroup_options').elements.ParentID.value = st.firstSelected() ? st.getIdxOf(st.firstSelected()) : 0;
Ajax.SubmitForm('addgroup_options', null, {
onSuccess : Ajax.Evaluator,
onFailure : function(response) {
errorMessage('Error adding page', response);
}
});
return false;
}
}
/**
* Delete page action
*/
deletegroup = {
button_onclick : function() {
/*if( $('deletegroup_options').style.display == 'none' )
$('deletegroup_options').style.display = 'block';
else
$('deletegroup_options').style.display = 'none';*/
if(treeactions.toggleSelection(this)) {
$('deletegroup_options').style.display = 'block';
deletegroup.o1 = $('sitetree').observeMethod('SelectionChanged', deletegroup.treeSelectionChanged);
deletegroup.o2 = $('deletegroup_options').observeMethod('Close', deletegroup.popupClosed);
addClass($('sitetree'),'multiselect');
deletegroup.selectedNodes = { };
var sel = $('sitetree').firstSelected();
if(sel && sel.className.indexOf('nodelete') == -1) {
var selIdx = $('sitetree').getIdxOf(sel);
deletegroup.selectedNodes[selIdx] = true;
sel.removeNodeClass('current');
sel.addNodeClass('selected');
}
} else
$('deletegroup_options').style.display = 'none';
return false;
},
treeSelectionChanged : function(selectedNode) {
var idx = $('sitetree').getIdxOf(selectedNode);
if(selectedNode.className.indexOf('nodelete') == -1) {
if(selectedNode.selected) {
selectedNode.removeNodeClass('selected');
selectedNode.selected = false;
deletegroup.selectedNodes[idx] = false;
} else {
selectedNode.addNodeClass('selected');
selectedNode.selected = true;
deletegroup.selectedNodes[idx] = true;
}
}
return false;
},
popupClosed : function() {
removeClass($('sitetree'),'multiselect');
$('sitetree').stopObserving(deletegroup.o1);
$('deletegroup_options').stopObserving(deletegroup.o2);
for(var idx in deletegroup.selectedNodes) {
if(deletegroup.selectedNodes[idx]) {
node = $('sitetree').getTreeNodeByIdx(idx);
if(node) {
node.removeNodeClass('selected');
node.selected = false;
}
}
}
},
form_submit : function() {
var csvIDs = "";
for(var idx in deletegroup.selectedNodes) {
if(deletegroup.selectedNodes[idx]) csvIDs += (csvIDs ? "," : "") + idx;
}
if(csvIDs) {
$('deletegroup_options').elements.csvIDs.value = csvIDs;
Ajax.SubmitForm('deletegroup_options', null, {
onSuccess : function(response) {
Ajax.Evaluator(response);
var sel;
if((sel = $('sitetree').firstSelected()) && sel.parentNode) sel.addNodeClass('current');
else $('Form_EditForm').innerHTML = "";
treeactions.closeSelection($('deletegroup'));
},
onFailure : function(response) {
errorMessage('Error deleting pages', response);
}
});
$('deletegroup').getElementsByTagName('a')[0].onclick();
} else {
alert("Please select at least 1 page.");
}
return false;
}
}
/**
* Initialisation function to set everything up
*/
Behaviour.addLoader(function () {
// Set up add page
Observable.applyTo($('addgroup_options'));
$('addgroup').onclick = addgroup.button_onclick;
$('addgroup').getElementsByTagName('a')[0].onclick = function() {return false;};
$('addgroup_options').onsubmit = addgroup.form_submit;
// Set up delete page
Observable.applyTo($('deletegroup_options'));
$('deletegroup').onclick = deletegroup.button_onclick;
$('deletegroup').getElementsByTagName('a')[0].onclick = function() {return false;};
$('deletegroup_options').onsubmit = deletegroup.form_submit;
});

View File

@ -0,0 +1,11 @@
/**
* CAUTION: Assumes that a MemberTableField-instance is present as an editing form
*/
function action_addmember_right() {
var memberTableFields = document.getElementsBySelector('#Form_EditForm div.MemberTableField');
var tables = document.getElementsBySelector('#Form_EditForm div.MemberTableField table');
var addLinks = document.getElementsBySelector('#Form_EditForm div.MemberTableField a.addlink');
try {
memberTableFields[0].openPopup(null,addLinks[0].href,tables[0]);
} catch(err) {}
}

70
javascript/SideReports.js Executable file
View File

@ -0,0 +1,70 @@
SideReports = Class.extend('SidePanel');
SideReports.prototype = {
initialize: function() {
this.selector = $('ReportSelector');
if(this.selector) this.selector.holder = this;
this.SidePanel.initialize();
},
destroy: function() {
if(this.SidePanel) this.SidePanel.destroy();
this.SidePanel = null;
if(this.selector) this.selector.holder = null;
this.selector = null;
},
onshow: function() {
if(this.selector.value) this.showreport();
},
/**
* Retrieve a report via ajax
*/
showreport: function() {
if(this.selector.value) {
this.body.innerHTML = '<p>loading...</p>';
this.ajaxGetPanel(this.afterPanelLoaded);
} else {
this.body.innerHTML = "<p>choose a report in the dropdown.</p>";
}
},
afterPanelLoaded : function() {
SideReportRecord.applyTo('#' + this.id + ' a');
},
ajaxURL: function() {
return 'admin/sidereport/' + this.selector.value;
}
}
SideReportDropdown = Class.create();
SideReportDropdown.prototype = {
destroy: function() {
this.onchange = null;
this.holder = null;
},
onchange: function() {
this.holder.showreport();
}
}
SideReportRecord = Class.create();
SideReportRecord.prototype = {
destroy: function() {
this.onclick = null;
},
onclick : function(event) {
Event.stop(event);
$('sitetree').loadingNode = $('sitetree').getTreeNodeByIdx( this.getID() );
$('Form_EditForm').getPageFromServer(this.getID());
},
getID : function() {
if(this.href.match(/\/([^\/]+)$/)) return parseInt(RegExp.$1);
}
}
SideReportDropdown.applyTo('#ReportSelector');
SideReportRecord.applyTo('#reports_holder a');
SideReports.applyTo('#reports_holder');

358
javascript/SideTabs.js Executable file
View File

@ -0,0 +1,358 @@
SideTabs = Class.create();
SideTabs.prototype = {
/**
* Set up the SideTab set. Note that tabs themselves are automatically constructed
* from the HTML.
*/
initialize: function() {
this.tabs = this.getElementsByTagName('li');
this.resize();
$('Form_EditForm').observeMethod('PageLoaded',this.onpagechanged.bind(this));
},
destroy: function() {
this.tabs = null;
},
/**
* Fix all the panes' sizes in reponse to one being opened or closed
*/
resize: function() {
var i,numOpenPanes=0,otherPanes = [];
for(i=0;i<this.tabs.length;i++) {
if(this.tabs[i].selected) {
numOpenPanes++;
otherPanes[otherPanes.length] = this.tabs[i].linkedPane;
}
}
if(numOpenPanes > 0) {
$('left').show();
var totalHeight = getFittingHeight(this.tabs[0].linkedPane, 0, otherPanes);
var eachHeight = totalHeight / numOpenPanes;
for(i=0;i<this.tabs.length;i++) {
if(this.tabs[i].selected) {
this.tabs[i].linkedPane.style.height = eachHeight + 'px';
if(this.tabs[i].linkedPane.onresize) this.tabs[i].linkedPane.onresize();
}
}
// All panes closed, hide the whole left-hand panel
} else {
$('left').hide();
}
},
/**
* Refresh all the panes after we change to a new page
*/
onpagechanged: function() {
var item,i;
for(i=0;item=this.tabs[i];i++) {
if(this.tabs[i].selected && this.tabs[i].linkedPane.onpagechanged) this.tabs[i].linkedPane.onpagechanged();
}
}
}
SideTabItem = Class.create();
SideTabItem.prototype = {
/**
* Set up one of the side tabs
*/
initialize: function() {
var holderID = this.id.replace('sidetab_','') + '_holder';
this.linkedPane = $(holderID);
if(!this.linkedPane) throw("Can't find item: " + holderID);
this.selected = (this.className && this.className.indexOf('selected') > -10);
this.holder = this.parentNode;
this.linkedPane.style.display = this.selected ? '' : 'none';
},
destroy: function() {
this.holder = null;
this.linkedPane = null;
this.onclick = null;
},
/**
* Handler for <li> click
*/
onclick: function(event) {
Event.stop(event);
this.toggle();
},
toggle: function() {
if(this.selected) this.close();
else this.open();
},
open: function() {
if(!this.selected) {
this.selected = true;
Element.addClassName(this,'selected');
this.linkedPane.style.display = '';
this.holder.resize();
if(this.linkedPane.onshow) {
this.linkedPane.onshow();
}
}
},
close: function() {
if(this.selected) {
this.selected = false;
Element.removeClassName(this,'selected');
this.linkedPane.style.display = 'none';
this.holder.resize();
if(this.holder.onhide) this.holder.onhide();
if(this.linkedPane.onclose) this.linkedPane.onclose();
}
}
}
// Application order is important - the Items must be created before the SideTabs object.
SideTabItem.applyTo('#SideTabs li');
SideTabs.applyTo('#SideTabs');
/**
* Generic base class for all side panels
*/
SidePanel = Class.create();
SidePanel.prototype = {
initialize : function() {
this.body = this.getElementsByTagName('div')[0];
},
destroy: function() {
this.body = null;
},
onshow : function() {
this.onresize();
this.body.innerHTML = '<p>loading...</p>';
this.ajaxGetPanel(this.afterPanelLoaded);
},
onresize : function() {
fitToParent(this.body);
},
ajaxGetPanel : function(onComplete) {
fitToParent(this.body);
new Ajax.Updater(this.body, this.ajaxURL(), {
onComplete : onComplete ? onComplete.bind(this) : null,
onFailure : this.ajaxPanelError
});
},
ajaxPanelError : function (response) {
errorMessage("error getting side panel", response);
},
ajaxURL : function() {
var srcName = this.id.replace('_holder','');
var id = $('Form_EditForm').elements.ID;
if(id) id = id.value; else id = "";
return 'admin/' + srcName + '/' + id + '?ajax=1';
},
afterPanelLoaded : function() {
},
onpagechanged : function() {
}
}
SidePanelRecord = Class.create();
SidePanelRecord.prototype = {
onclick : function(event) {
Event.stop(event);
$('Form_EditForm').getPageFromServer(this.getID());
},
destroy: function() {
this.onclick = null;
},
getID : function() {
if(this.href.match(/\/([^\/]+)$/)) return parseInt(RegExp.$1);
}
}
/**
* Class that will add an 'over' class when the mouse is over the object
*/
Highlighter = Class.create();
Highlighter.prototype = {
onmouseover: function() {
Element.addClassName(this,'over');
},
onmouseout: function() {
Element.removeClassName(this,'over');
},
destroy: function() {
this.onmouseover = null;
this.onmouseout = null;
},
select: function(dontHide) {
if(dontHide) {
Element.addClassName(this,'current');
this.parentNode.lastSelected = null;
} else {
if(this.parentNode.lastSelected) {
Element.removeClassName(this.parentNode.lastSelected,'current');
Element.addClassName(this,'current');
} else {
var i,item;
for(i=0;item=this.parentNode.childNodes[i];i++) {
if(item.tagName) {
if(item == this) Element.addClassName(item,'current');
else Element.removeClassName(item,'current');
}
}
}
this.parentNode.lastSelected = this;
}
}
}
/**
* Version list
*/
VersionList = Class.extend('SidePanel');
VersionList.prototype = {
initialize : function() {
this.mode = 'view';
this.SidePanel.initialize();
},
destroy: function() {
this.SidePanel = null;
this.onclose = null;
},
ajaxURL : function() {
return this.SidePanel.ajaxURL() + '&unpublished=' + ($('versions_showall').checked ? 1 : 0);
},
afterPanelLoaded : function() {
this.idLoaded = $('Form_EditForm').elements.ID.value;
VersionItem.applyTo('#' + this.id + ' tbody tr');
},
select : function(pageID, versionID, sourceEl) {
if(this.mode == 'view') {
sourceEl.select();
$('Form_EditForm').loadURLFromServer('admin/getversion/' + pageID + '/' + versionID);
$('viewArchivedSite').style.display = '';
$('viewArchivedSite').getVars = '?archiveDate=' + sourceEl.getElementsByTagName('td')[1].className;
} else {
$('viewArchivedSite').style.display = 'none';
if(this.otherVersionID) {
sourceEl.select();
this.otherSourceEl.select(true);
statusMessage('Loading comparison...');
$('Form_EditForm').loadURLFromServer('admin/compareversions/' + pageID + '/?From=' + this.otherVersionID + '&To=' + versionID);
} else {
sourceEl.select();
}
}
this.otherVersionID = versionID;
this.otherSourceEl = sourceEl;
},
onpagechanged : function() {
if(this.idLoaded != $('Form_EditForm').elements.ID.value) {
this.body.innerHTML = '<p>loading...</p>';
this.ajaxGetPanel(this.afterPanelLoaded);
}
},
refresh : function() {
this.ajaxGetPanel(this.afterPanelLoaded);
},
onclose : function() {
$('Form_EditForm').getPageFromServer(this.idLoaded);
$('viewArchivedSite').style.display = 'none';
}
}
VersionItem = Class.extend('Highlighter');
VersionItem.prototype = {
initialize : function() {
this.holder = $('versions_holder');
},
destroy: function() {
this.holder = null;
this.onclick = null;
},
onclick : function() {
this.holder.select(this.pageID(), this.versionID(), this);
},
idPair : function() {
if(this.id.match(/page-([^-]+)-version-([^-]+)$/)) {
return RegExp.$1 + '/' + RegExp.$2;
}
},
pageID : function() {
if(this.id.match(/page-([^-]+)-version-([^-]+)$/)) {
return RegExp.$1;
}
},
versionID : function() {
if(this.id.match(/page-([^-]+)-version-([^-]+)$/)) {
return RegExp.$2;
}
}
}
VersionAction = Class.create();
VersionAction.prototype = {
initialize: function() {
this.holder = $('versions_holder');
this.selector = this.getElementsByTagName('select')[0];
this.selector.holder = this;
this.selector.onchange = function() { this.holder.changed(this.value,this); return false; }
this.showallCheckbox = $('versions_showall');
this.showallCheckbox.holder = this;
this.showallCheckbox.onclick = this.showall_change;
},
showall_change: function() {
this.holder.holder.refresh();
},
destroy: function() {
if(this.selector) {
this.selector.holder = null;
this.selector.onchange = null;
}
if(this.showallCheckbox) {
this.showallCheckbox.holder = null;
this.showallCheckbox.onchange = null;
}
this.holder = null;
this.selector = null;
},
/**
* Handler function whenever an action is clicked
*/
changed: function(url, selector) {
switch(url) {
case 'view': return this.view();
case 'compare': return this.compare();
default: errorMessage("VersionAction.changed: I don't know about action '" + url + "'");
}
},
view: function() {
this.holder.mode = 'view';
},
compare: function() {
this.holder.mode = 'compare';
}
}
VersionItem.applyTo('#versions_holder tbody tr');
VersionAction.applyTo('#versions_holder p.pane_actions');
VersionList.applyTo('#versions_holder');

21
javascript/TaskList.js Executable file
View File

@ -0,0 +1,21 @@
TaskList = Class.extend('SidePanel');
TaskList.prototype = {
destroy: function() {
if(this.SidePanel) this.SidePanel.destroy();
this.SidePanel = null;
},
/**
* Called after the panel has been ajax-loaded in
*/
afterPanelLoaded : function() {
TaskListRecord.applyTo('#' + this.id + ' a');
}
}
TaskListRecord = SidePanelRecord;
TaskListRecord.applyTo('#tasklist_holder a');
TaskList.applyTo('#tasklist_holder');
TaskListRecord.applyTo('#waiting_holder a');
TaskList.applyTo('#waitingon_holder');

View File

@ -0,0 +1,56 @@
ThumbnailStripField = Class.create();
// We do this instead of div.thumbnailstrip for efficiency. It means that ThumbnailStripField can only be used in the
// CMS toolbar
ThumbnailStripField.applyTo('#Image');
ThumbnailStripField.applyTo('#Flash');
ThumbnailStripField.prototype = {
/**
* @var updateMethod string Specifies the Ajax-call for getting files
* (currently either "getimages" or "getflash"). This can be specified
* in the PHP-constructor of ThumbnailStripField and is passed to the client
* as a fake css-class.
*/
updateMethod: "getimages",
initialize: function() {
try {
this.updateMethod = this.className.match(/updatemethod=([^ ]+)/)[1];
} catch(err) {}
if(this.className.match(/parent=([^ ]+)/)) {
// HACK: This is hard-coded to only work with TreeDropdownFields
var parentField = $(RegExp.$1).parentNode;
if(parentField) {
parentField.observeMethod('Change', this.ajaxGetFiles.bind(this));
}
}
},
ajaxGetFiles: function(folderID) {
this.innerHTML = '<span style="float: left">Loading...</span>'
var ajaxURL = this.helperURLBase() + '&methodName='+this.updateMethod+'&folderID=' + folderID;
new Ajax.Updater(this, ajaxURL, {
method : 'get',
onComplete : this.reapplyBehaviour.bind(this),
onFailure : function(response) { errorMessage("Error getting files", response); }
})
},
reapplyBehaviour: function() {
Behaviour.apply(this);
},
helperURLBase: function() {
var fieldName = this.id; //this.id.replace(this.ownerForm().name + '_','');
return this.ownerForm().action + '&action_callfieldmethod=1&fieldName=' + fieldName + '&ajax=1'
},
ownerForm: function() {
var f =this.parentNode;
while(f && f.tagName.toLowerCase() != 'form') f = f.parentNode;
return f;
}
}

10
javascript/dialog.js Normal file
View File

@ -0,0 +1,10 @@
Behaviour.register({
'.buttons button' : {
onclick: function() {
window.top._OPEN_DIALOG.execHandler( this.name );
return false;
}
}
});

42
javascript/tinymce.template.js Executable file
View File

@ -0,0 +1,42 @@
function nullConverter(url) {
return url;
}
/**
* TinyMCE initialisation template.
* $ variables are replaced by string search & replace. It's pretty crude.
*/
tinyMCE.init({
mode : "specific_textareas",
textarea_trigger : "tinymce",
width: -1,
height: -1,
auto_resize : true,
theme : "advanced",
content_css : "$ContentCSS",
document_base_url: "$BaseURL",
urlconverter_callback : "nullConverter",
setupcontent_callback : "sapphiremce_setupcontent",
cleanup_callback : "sapphiremce_cleanup",
Theme_Advanced_Layout_manager : "SimpleLayout",
theme_advanced_toolbar_location : "manually_placed",
theme_advanced_toolbar_align : "left",
theme_advanced_toolbar_parent : "right",
plugins : "contextmenu,table,emotions,flash",
table_inline_editing : true,
theme_advanced_buttons1 : "italic,underline,strikethrough,separator,justifyleft,justifycenter,justifyright,justifyfull,styleselect,formatselect,separator,bullist,numlist,outdent,indent,hr,charmap",
theme_advanced_buttons2 : "undo,redo,separator,cut,copy,paste,separator,search,replace,separator,flash",
theme_advanced_buttons3 : "",
theme_advanced_buttons3_add : "emotions",
/*
theme_advanced_buttons1 : "bold,italic,underline,strikethrough,separator,justifyleft,justifycenter,justifyright,justifyfull,styleselect,formatselect,separator,bullist,numlist,outdent,indent,hr,charmap",
theme_advanced_buttons2 : "undo,redo,separator,cut,copy,paste,separator,search,replace,separator,link,unlink,anchor,image,separator,cleanup,removeformat,visualaid,code,separator,tablecontrols",
*/
safari_warning : false,
relative_urls : true,
verify_html : true,
valid_elements : "+a[id|rel|rev|dir|tabindex|accesskey|type|name|href|target|title|class],-strong/-b[class],-em/-i[class],-strike[class],-u[class],#p[id|dir|class|align],-ol[class],-ul[class],-li[class],br,img[id|dir|longdesc|usemap|class|src|border|alt=|title|width|height|align],-sub[class],-sup[class],-blockquote[dir|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|dir|id|style],-tr[id|dir|class|rowspan|width|height|align|valign|bgcolor|background|bordercolor|style],tbody[id|class|style],thead[id|class|style],tfoot[id|class|style],-td[id|dir|class|colspan|rowspan|width|height|align|valign|scope|style],-th[id|dir|class|colspan|rowspan|width|height|align|valign|scope|style],caption[id|dir|class],-div[id|dir|class|align],-span[class|align],-pre[class|align],address[class|align],-h1[id|dir|class|align],-h2[id|dir|class|align],-h3[id|dir|class|align],-h4[id|dir|class|align],-h5[id|dir|class|align],-h6[id|dir|class|align],hr[class],dd[id|class|title|dir],dl[id|class|title|dir],dt[id|class|title|dir]",
extended_valid_elements : "img[class|src|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name]"
});

1
silverstripe_version Normal file
View File

@ -0,0 +1 @@
$URL$

View File

@ -0,0 +1,24 @@
<html>
<head>
<style>
body {
padding: 0;
margin: 0;
background-color: #fff !important;
}
fieldset {
padding: 0;
margin: 0;
border-style: none;
}
</style>
<% base_tag %>
</head>
<body>
<% if CanUpload %>
$UploadForm
<% else %>
You do not have permission to upload files into this folder.
<% end_if %>
</body>

View File

@ -0,0 +1 @@
$EditForm

View File

@ -0,0 +1,9 @@
<p>$Message
Results
<% control Results %>
list changes here
<% end_control %>
$ConfirmForm

36
templates/CMSMain_dialog.ss Executable file
View File

@ -0,0 +1,36 @@
<html>
<head>
<script>
function buttonClick(el) {
<% if Modal %>
window.returnValue = el.name;
window.close();
<% else %>
if(window.linkedObject) {
if(el.name) {
window.linkedObject.result = el.name;
} else {
throw("Couldn't find the button name");
}
} else {
throw("Can't find window.linkedObject to send the button click back to the main window");
}
window.close();
<% end_if %>
}
</script>
</head>
<body>
<p>$Message</p>
<p style="align: center">
<% control Buttons %>
<input type="button" value="$Title" name="$Name" onclick="buttonClick(this);" />
<% end_control %>
</p>
</body>
</html>

30
templates/CMSMain_versions.ss Executable file
View File

@ -0,0 +1,30 @@
<table id="Versions">
<thead>
<tr>
<td>#</td>
<td>When</td>
<td>Author</td>
<td>Publisher</td>
</tr>
</thead>
<tbody>
<% control Versions %>
<tr id="page-$RecordID-version-$Version" class="$EvenOdd $PublishedClass">
<td>$Version</td>
<td class="$LastEdited" title="$LastEdited.Ago">$LastEdited.Nice</td>
<td>$Author.FirstName $Author.Surname.Initial</td>
<td>
<% if Published %>
<% if Publisher %>
$Publisher.FirstName $Publisher.Surname.Initial
<% else %>
Unknown
<% end_if %>
<% else %>
Not published
<% end_if %>
</td>
</tr>
<% end_control %>
</tbody>
</table>

14
templates/CommentList.ss Executable file
View File

@ -0,0 +1,14 @@
<% if Comments %>
<ul id="CommentList">
<% control Comments %>
<li class="$EvenOdd">
$Comment
<div class="extra">$Action $Created.Ago - $Author.FirstName&nbsp;$Author.Surname.Initial</div>
</li>
<% end_control %>
</ul>
<% else %>
<p>There are no comments on this page.</p>
<p>Comments are created whenever one of the 'workflow actions'
are undertaken - Publish, Reject, Submit.</p>
<% end_if %>

14
templates/Dialog.ss Normal file
View File

@ -0,0 +1,14 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<% base_tag %>
<title>$Title</title>
</head>
<body class="$DialogType">
<p class="message"><img src="cms/images/dialogs/{$DialogType}.gif" /><span>$Message</span></p>
<div class="buttons">
$Buttons
</div>
</body>
</html>

View File

@ -0,0 +1,52 @@
<script>
_TREE_ICONS = {};
_TREE_ICONS['Folder'] = {
fileIcon: 'jsparty/tree/images/page-closedfolder.gif',
openFolderIcon: 'jsparty/tree/images/page-openfolder.gif',
closedFolderIcon: 'jsparty/tree/images/page-closedfolder.gif'
};
</script>
<div class="title"><div style="background-image : url(cms/images/panels/MySite.png)">Files &amp; Images</div></div>
<div id="treepanes">
<div id="sitetree_holder">
<ul id="TreeActions">
<li class="action" id="addpage"><a href="admin/addpage">Create</a></li>
<li class="action" id="deletepage"><a href="admin/deletepage">Delete</a></li>
<li class="action" id="sortitems"><a href="#">Reorganise</a></li>
</ul>
<div style="clear:both;"></div>
<form class="actionparams" id="addpage_options" style="display: none" action="admin/assets/addfolder">
<div>
<input type="hidden" name="ParentID" />
<input class="action" type="submit" value="Go" />
</div>
</form>
<form class="actionparams" id="deletepage_options" style="display: none" action="admin/assets/deletefolder">
<p>Select the folders that you want to delete and then click the button below</p>
<div>
<input type="hidden" name="csvIDs" />
<input type="submit" value="Delete the selected folders" />
</div>
</form>
<form class="actionparams" id="sortitems_options" style="display: none">
<p id="sortitems_message" style="margin: 0">To reorganise your folders, drag them around as desired.</p>
</form>
<!--
<ul style="float: left" id="SideTabs">
<li id="sidetab_sitetree" class="selected">Site Map</li>
<li id="sidetab_tasklist">Task List</li>
<li id="sidetab_waitingon">Waiting on</li>
<li id="sidetab_versions">Versions</li>
<li id="sidetab_comments">Comments</li>
<li id="sidetab_reports">Reports</li>
</ul>
-->
$SiteTreeAsUL
</div>
</div>

View File

@ -0,0 +1,34 @@
<div class="title"><div >Folder contents</div></div>
<div id="form_actions_right" class="ajaxActions">
</div>
$MoveMarkedOptionsForm
$DeleteMarkedOptionsForm
<!--
<form class="actionparams" id="action_movemarked_options" style="display:none" action="admin/assets/movemarked">
<fieldset>
<input type="hidden" name="ID" />
<div class="field">
<label class="left">Move files to</label>
$ChooseFolderField
</div>
</fieldset>
<p class="actions">
<input type="submit" value="Move marked files" />
</p>
</form>
-->
<% if EditForm %>
$EditForm
<% else %>
<form id="Form_EditForm" action="admin/assets/?executeForm=EditForm" method="post" enctype="multipart/form-data">
<h1>$ApplicationName</h1>
<p>Welcome to $ApplicationName! Please choose a page from the left.</p>
</form>
<% end_if %>
<p id="statusMessage" style="visibility:hidden"></p>

View File

@ -0,0 +1,27 @@
<div id="$id" class="$Classes">
<% include TableListField_PageControls %>
<table class="data">
<thead>
<tr>
<% if Markable %><th width="18">&nbsp;</th><% end_if %>
<% control Headings %>
<th class="$Name">$Title</th>
<% end_control %>
<th width="18">&nbsp;</th>
</tr>
</thead>
<tbody>
<% control Items %>
<tr id="record-$Parent.Name-$ID">
<% if Markable %><td width="18" class="markingcheckbox">$MarkingCheckbox</td><% end_if %>
<% control Fields %>
<td>$Value</td>
<% end_control %>
<td width="18">
<a class="popuplink editlink" href="$EditLink" target="_blank"><img src="cms/images/edit.gif" alt="edit" /></a>
</td>
</tr>
<% end_control %>
</tbody>
</table>
</div>

View File

@ -0,0 +1,20 @@
<div class="title"><div>Functions</div></div>
<div id="treepanes">
<div id="sitetree_holder" style="overflow:auto">
<% if BulkLoaders %>
<ul id="sitetree" class="tree unformatted">
<li id="$ID" class="root Root"><a>Batch entry functions</a>
<ul>
<% control BulkLoaders %>
<li id="record-$class">
<a href="admin/bulkload/show/$class">$Title</a>
</li>
<% end_control %>
</ul>
</li>
</ul>
<% end_if %>
</div>
</div>

View File

@ -0,0 +1,7 @@
<div class="title"><div>Bulk loader</div></div>
<form id="Form_EditForm" action="admin/bulkload?executeForm=EditForm" method="post" enctype="multipart/form-data">
<iframe style="width: 100%; height: 100%" src="admin/bulkload/iframe/$Class"></iframe>
</form>
<p id="statusMessage" style="visibility:hidden"></p>

28
templates/Includes/CMSLeft.ss Executable file
View File

@ -0,0 +1,28 @@
<h1>Site Content</h1>
<ul id="TreeActions">
<li class="action" id="addpage"><a href="admin/addpage">New Page...</a></li>
<li class="action" id="deletepage"><a href="admin/deletepage">Delete Pages...</a></li>
<li class="spacer"></li>
</ul>
<form class="actionparams" id="addpage_options" style="display: none" action="admin/addpage">
<select name="Type">
<% control PageTypes %>
<option value="$ClassName">$AddAction</option>
<% end_control %>
</select>
<input type="hidden" name="ParentID" />
<input class="action" type="submit" value="Go" />
</form>
<form class="actionparams" id="deletepage_options" style="display: none" action="admin/deletepages">
<p>Select the pages that you want to delete and then click the button below</p>
<input type="hidden" name="csvIDs" />
<input type="submit" value="Delete the selected pages" />
</form>
<div id="sitetree_holder" style="overflow:auto">
$SiteTreeAsUL
</div>

View File

@ -0,0 +1,130 @@
<div class="title"><div style="background-image : url(cms/images/panels/MySite.png)">My Site</div></div>
<ul style="float: left; clear: left;" id="SideTabs">
<li id="sidetab_sitetree" class="selected">Site Map</li>
<!--<li id="sidetab_search">Search</li>-->
<% if EnterpriseCMS %>
<li id="sidetab_tasklist">Task List</li>
<li id="sidetab_waitingon">Waiting on</li>
<% end_if %>
<li id="sidetab_versions">Versions</li>
<% if EnterpriseCMS %>
<li id="sidetab_comments">Comments</li>
<% end_if %>
<li id="sidetab_reports">Reports</li>
</ul>
<div id="treepanes">
<div id="sitetree_holder">
<ul id="TreeActions">
<li class="action" id="addpage"><a href="admin/addpage">Create</a></li>
<li class="action" id="deletepage"><a href="admin/deletepage">Delete</a></li>
<li class="action" id="sortitems"><a href="#">Reorganise</a></li>
<!-- <li class="action" id="duplicate"><a href="#">Duplicate</a></li>
Sam: this should be put into the Create area, I think, so we don't stuff up the layout -->
</ul>
<div style="clear:both;"></div>
<% control AddPageOptionsForm %>
<form class="actionparams" id="$FormName" style="display: none" action="$FormAction">
<% control Fields %>
$FieldHolder
<% end_control %>
<!--
<div>
<select name="Type">
<% control PageTypes %>
<option value="$ClassName">$AddAction</option>
<% end_control %>
</select>
<input type="hidden" name="ParentID" />
</div>
-->
<div>
<input class="action" type="submit" value="Go" />
</div>
</form>
<% end_control %>
<form class="actionparams" id="deletepage_options" style="display: none" action="admin/deleteitems">
<p>Select the pages that you want to delete and then click the button below</p>
<div>
<input type="hidden" name="csvIDs" />
<input type="submit" value="Delete the selected pages" />
</div>
</form>
<form class="actionparams" id="sortitems_options" style="display: none">
<p id="sortitems_message" style="margin: 0">To reorganise your site, drag the pages around as desired.</p>
</form>
<% control DuplicatePagesOptionsForm %>
<form class="actionparams" id="duplicate_options" style="display: none" action="admin/duplicateSiteTree">
<p>Select the pages that you want to duplicate, whether it's children should be included, and where you want the duplicates placed</p>
<div>
<input type="hidden" name="csvIDs" />
<input type="submit" value="Duplicate" />
</div>
</form>
<% end_control %>
<div id="publication_key" style="border-bottom: 1px #CCC solid; background-color: #EEE; padding: 3px;">
Key:
<ins style="cursor: help" title="Added to the stage site and not published yet">new</ins>
<del style="cursor: help" title="Deleted from the stage site but still on the live site">deleted</del>
<span style="cursor: help" title="Edited on the stage site and not published yet" class="modified">changed</span>
</div>
$SiteTreeAsUL
</div>
<!--<div id="search_holder" style="display:none">
<h2>Search</h2>
<div class="unitBody">
</div>
</div>-->
<% if EnterpriseCMS %>
<div class="listpane" id="tasklist_holder" style="display:none">
<h2>Tasklist</h2>
<div class="unitBody">
</div>
</div>
<div class="listpane" id="waitingon_holder" style="display:none">
<h2>Waiting on</h2>
<div class="unitBody">
</div>
</div>
<% end_if %>
<div class="listpane" id="versions_holder" style="display:none">
<h2>History</h2>
<p class="pane_actions" id="versions_actions">
<select>
<option value="view" selected="selected">View (click to see)</option>
<option value="compare" >Compare (click 2 to see)</option>
</select>
<br />
<input type="checkbox" id="versions_showall" /> Show unpublished versions
</p>
<div class="unitBody">
</div>
</div>
<% if EnterpriseCMS %>
<div class="listpane" id="comments_holder" style="display:none">
<h2>Comments</h2>
<div class="unitBody">
</div>
</div>
<% end_if %>
<div class="listpane" id="reports_holder" style="display:none">
<h2>Reports</h2>
<p id="ReportSelector_holder">$ReportSelector</p>
<div class="unitBody">
</div>
</div>
</div>

View File

@ -0,0 +1,38 @@
<div class="title"><div style="background-image : url(cms/images/panels/EditPage.png)">Edit Page</div></div>
<% include Editor_toolbar %>
<div id="form_actions_right" class="ajaxActions">
</div>
<form class="actionparams" id="action_submit_options" style="display:none" action="admin/submit">
<fieldset>
<input type="hidden" name="ID" />
<div class="field" id="action_submit_options_recipient">
<label class="left">Send to</label>
<span>loading...</span>
</div>
<div class="field" id="action_submit_options_status">
<label class="left">Status</label>
<input type="hidden" name="Status" />
<span></span>
</div>
<p class="label">Do you have any message for your editor?</p>
<textarea name="Message" rows="4" cols="20"></textarea>
</fieldset>
<p class="actions">
<input type="submit" value="Submit for approval" />
</p>
</form>
<% if EditForm %>
$EditForm
<% else %>
<form id="Form_EditForm" action="admin?executeForm=EditForm" method="post" enctype="multipart/form-data">
<h1>$ApplicationName</h1>
<p>Welcome to $ApplicationName! Please choose a page from the left.</p>
</form>
<% end_if %>
<p id="statusMessage" style="visibility:hidden"></p>

21
templates/Includes/CMSRight.ss Executable file
View File

@ -0,0 +1,21 @@
<h1>Edit Content</h1>
<div class="mceToolbar_background">&nbsp;</div>
<p id="statusMessage"></p>
<div id="form_actions">
</div>
<% if EditForm %>
$EditForm
<% else %>
<h1>$ApplicationName</h1>
<div class="mceToolbar_background">&nbsp;</div>
<p id="statusMessage" style="display:none"></p>
<form id="Form_EditForm" action="admin?executeForm=EditForm" method="post" enctype="multipart/form-data">
<p>Welcome to $ApplicationName! Please choose a page from the left.</p>
</form>
<% end_if %>
</body>

View File

@ -0,0 +1,41 @@
<% control EditorToolbar %>
<div class="mceToolbarExternal" id="mce_editor_toolbar">
<table width="100%" border="0">
<tbody>
<tr>
<td>
<!--
<a title="Jump to tool buttons - Alt+Q, Jump to editor - Alt-Z, Jump to element path - Alt-X" accesskey="q" href="#">
</a>
-->
<% control Buttons %>
<% if Type = button %>
<a href="#$Command">
<img width="20" height="20" class="mceButtonNormal" title="$Title" alt="$Title" src="$Icon" id="mce_editor_$IDSegment" />
</a>
<% else_if Type = dropdown %>
<select name="$Command" class="mceSelectList" id="mce_editor_$IDSegment">$Options</select>
<% else_if Type = separator %>
<img width="1" height="15" class="mceSeparatorLine" src="{$MceRoot}themes/advanced/images/spacer.gif" alt="|" />
<% else_if Type = break %>
<br />
<% end_if %>
<% end_control %>
<br />
<!--<a onfocus="tinyMCE.getInstanceById('mce_editor_0').getWin().focus();" accesskey="z" href="#">
</a>-->
</td>
</tr>
</tbody>
</table>
</div>
$LinkForm
$ImageForm
$FlashForm
<% end_control %>

View File

@ -0,0 +1,28 @@
<style>
#left_separator {
border-bottom: 3px solid #d4d0c8;
}
#SearchFrom_holder{
border-bottom: 1px solid #808080;
}
</style>
<div id="LeftPane">
<h2>Add Listing</h2>
<div id="SearchForm_holder" class="lefttop" style="overflow:auto">
$AddForm
</div>
<h2>Search Listings</h2>
<div id="SearchForm_holder" class="leftbottom" style="overflow:auto">
$SearchForm
</div>
<div id="left_separator">
&nbsp;
</div>
<h2>Search Results</h2>
<div id="ResultTable_holder" class="leftbottom" style="overflow:auto">
$Results
</div>
</div>

View File

@ -0,0 +1,16 @@
<div class="title"><div style="background-image : url(cms/images/panels/EditPage.png)">Edit Page</div></div>
<% include Editor_toolbar %>
<% if EditForm %>
$EditForm
<% else %>
<form id="Form_EditForm" action="admin?executeForm=EditForm" method="post" enctype="multipart/form-data">
<h1>$ApplicationName</h1>
<p>Welcome to $ApplicationName! Please choose click on one of the entries on the left pane.</p>
</form>
<% end_if %>
<p id="statusMessage" style="visibility:hidden"></p>

View File

@ -0,0 +1,15 @@
<div class="PageControls">
<input name="MemberListStart" id="MemberListStart" type="hidden" value="$MemberListStart" />
<% if LastLink %><a class="Last" href="$LastLink" title="View last $PageSize members"><img src="cms/images/pagination/record-last.png" alt="View last $PageSize members" /></a>
<% else %><span class="Last"><img src="cms/images/pagination/record-last-g.png" alt="View last $PageSize members" /></span><% end_if %>
<% if FirstLink %><a class="First" href="$FirstLink" title="View first $PageSize members"><img src="cms/images/pagination/record-first.png" alt="View first $PageSize members" /></a>
<% else %><span class="First"><img src="cms/images/pagination/record-first-g.png" alt="View first $PageSize members" /></span><% end_if %>
<% if PrevLink %><a class="Prev" href="$PrevLink" title="View previous $PageSize members"><img src="cms/images/pagination/record-prev.png" alt="View previous $PageSize members" /></a>
<% else %><img class="Prev" src="cms/images/pagination/record-prev-g.png" alt="View previous $PageSize members" /><% end_if %>
<span class="Count">
Displaying $FirstMember to $LastMember of $TotalMembers
</span>
<% if NextLink %><a class="Next" href="$NextLink" title="View next $PageSize members"><img src="cms/images/pagination/record-next.png" alt="View next $PageSize members" /></a>
<% else %><img class="Next" src="cms/images/pagination/record-next-g.png" alt="View next $PageSize members" /><% end_if %>
</div>

View File

@ -0,0 +1,42 @@
<% include MemberList_PageControls %>
<table class="CMSList MemberList">
<thead>
<tr>
<td class="FirstName">First Name</td>
<td class="Surname">Surname</td>
<td class="Email">Email Address</td>
<% if DontShowPassword %>
<% else %>
<td class="Password">Password</td>
<% end_if %>
<td>&nbsp;</td>
</tr>
</thead>
<tbody>
<% if DontShowPassword %>
<% control Members %>
<tr id="member-$ID">
<td>$FirstName</td>
<td>$Surname</td>
<td>$Email</td>
<td><a class="deletelink" href="admin/security/removememberfromgroup/$GroupID/$ID"><img src="cms/images/delete.gif" alt="delete" /></a></td>
</tr>
<% end_control %>
<% else %>
<% control Members %>
<tr id="member-$ID">
<td>$FirstName</td>
<td>$Surname</td>
<td>$Email</td>
<td>$Password</td>
<td><a class="deletelink" href="admin/security/removememberfromgroup/$GroupID/$ID"><img src="cms/images/delete.gif" alt="delete" /></a></td>
</tr>
<% end_control %>
<% end_if %>
$AddRecordForm.AsTableRow
</tbody>
</table>

View File

@ -0,0 +1,77 @@
<div class="$Classes" id="$id">
<div class="MemberFilter">
$SearchForm
</div>
<div id="MemberList">
<% include TableListField_PageControls %>
<table class="data">
<thead>
<tr>
<% if Markable %><th width="18">&nbsp;</th><% end_if %>
<% control Headings %>
<th class="$Name">$Title</th>
<% end_control %>
<% if Can(show) %><th width="18">&nbsp;</th><% end_if %>
<% if Can(edit) %><th width="18">&nbsp;</th><% end_if %>
<% if Can(delete) %><th width="18">&nbsp;</th><% end_if %>
</tr>
</thead>
<tfoot>
<tr class="addtogrouprow">
<% if Markable %><td width="18">&nbsp;</dh><% end_if %>
$AddRecordForm.CellFields
<td class="actions" colspan="2">$AddRecordForm.CellActions</td>
</tr>
<tr style="display: none;">
<% if Markable %><td width="18">&nbsp;</td><% end_if %>
<td colspan="$ItemCount">
<a class="popuplink addlink" href="$AddLink" alt="add"><img src="cms/images/add.gif" alt="add" /></a><a class="popuplink addlink" href="$AddLink" alt="add">Add new $Title</a>
</td>
<% if Can(show) %><td width="18">&nbsp;</td><% end_if %>
<% if Can(edit) %><td width="18">&nbsp;</td><% end_if %>
<% if Can(delete) %><td width="18">&nbsp;</td><% end_if %>
</tr>
</tfoot>
<tbody>
<% if Items %>
<% control Items %>
<tr id="record-$Parent.Name-$ID">
<% if Markable %><td width="18" class="markingcheckbox">$MarkingCheckbox</td><% end_if %>
<% control Fields %>
<td>$Value</td>
<% end_control %>
<% if Can(show) %>
<td width="18">
<a class="popuplink showlink" href="$ShowLink" title="Show this member" target="_blank"><img src="cms/images/show.png" alt="show" /></a>
</td>
<% end_if %>
<% if Can(edit) %>
<td width="18">
<a class="popuplink editlink" href="$EditLink" title="Edit this member" target="_blank"><img src="cms/images/edit.gif" alt="edit" /></a>
</td>
<% end_if %>
<% if Can(delete) %>
<td width="18">
<a class="deletelink" href="$DeleteLink" title="Delete this member"><img src="cms/images/delete.gif" alt="delete" /></a>
</td>
<% end_if %>
</tr>
<% end_control %>
<% else %>
<tr class="notfound">
<% if Markable %><th width="18">&nbsp;</th><% end_if %>
<td colspan="$Headings.Count"><i>No $NamePlural found</i></td>
<% if Can(show) %><td width="18">&nbsp;</td><% end_if %>
<% if Can(edit) %><td width="18">&nbsp;</td><% end_if %>
<% if Can(delete) %><td width="18">&nbsp;</td><% end_if %>
</tr>
<% end_if %>
</tbody>
</table>
<div class="utility">
<% if Can(export) %>
$ExportButton
<% end_if %>
</div>
</div>
</div>

View File

@ -0,0 +1,24 @@
<% if Entries %>
<table class="CMSList BouncedList" summary="Emails that have bounced">
<thead>
<tr>
<th>User name</th>
<th>Email address</th>
<th>Reason:</th>
<th>Last bounce at</th>
</tr>
</thead>
<tbody>
<% control Entries %>
<tr>
<td>$Member.FirstName $Member.Surname</td>
<td>$Member.Email</td>
<td>$Record.BounceMessage</td>
<td>$Record.Created.Long</td>
</tr>
<% end_control %>
</tbody>
</table>
<% else %>
<p>No emails sent have bounced.</p>
<% end_if %>

View File

@ -0,0 +1,30 @@
<% if NewsletterTypes %>
<ul id="sitetree" class="tree unformatted">
<% control NewsletterTypes %>
<li id="mailtype_$ID" class="MailType">
<a href="#">$Title</a>
<ul>
<li id="drafts_$ID" class="DraftFolder nodelete"><a href="$baseURL/admin/showtype/$ID">Drafts</a>
<% if DraftNewsletters %>
<ul>
<% control DraftNewsletters %>
<li class="Draft" id="draft_{$ParentID}_{$ID}"><a href="$baseURL/admin/newsletter/shownewsletter/$ID">$Title</a></li>
<% end_control %>
</ul>
<% end_if %>
</li>
<li id="sent_$ID" class="SentFolder nodelete"><a href="$baseURL/admin/showtype/$ID">Sent Items</a>
<% if SentNewsletters %>
<ul>
<% control SentNewsletters %>
<li class="Sent" id="sent_{$ParentID}_{$ID}"><a href="$baseURL/admin/newsletter/shownewsletter/$ID">$Title</a></li>
<% end_control %>
</ul>
<% end_if %>
</li>
<li id="recipients_$ID" class="Recipients nodelete"><a href="$baseURL/admin/newsletter/showtype/$ID">Mailing List</a></li>
</ul>
</li>
<% end_control %>
</ul>
<% end_if %>

View File

@ -0,0 +1,20 @@
<% if Entries %>
<table class="CMSList UnsubscribedList" summary="Unsubscribed users">
<tbody>
<tr>
<td>User name</td>
<td>Unsubscribed on</td>
</tr>
<% control Entries %>
<tr>
<td>$Member.FirstName $Member.Surname</td>
<td>$Record.Created.Long</td>
</tr>
<% end_control %>
</tbody>
</table>
<% else %>
<p>
No users have unsubscribed from this newsletter.
</p>
<% end_if %>

View File

@ -0,0 +1,50 @@
<style>
ul.tree li.MailType span.MailType a {
background-image: url(cms/images/treeicons/sent-folder.png);
font-weight: bold;
}
ul.tree li.MailType span.Recipients a {
background-image: url(cms/images/treeicons/multi-user.png);
}
ul.tree li.MailType span.Recipients a {
background-image: url(cms/images/treeicons/multi-user.png);
}
ul.tree li.MailType span.SentFolder a, ul.tree span.SentFolder.children a {
background-image: url(cms/images/treeicons/sent-folder.png);
}
ul.tree li.MailType span.DraftFolder a, ul.tree span.DraftFolder.children a {
background-image: url(cms/images/treeicons/draft-folder.png);
}
ul.tree li.MailType span.Draft a {
background-image: url(cms/images/treeicons/draft-file.png);
}
ul.tree li.MailType span.Sent a {
background-image: url(cms/images/treeicons/sent-file.gif);
}
</style>
<div class="title"><div>Newsletters</div></div>
<div id="treepanes">
<div id="sitetree_holder">
<ul id="TreeActions">
<li class="action" id="addtype"><a href="admin/newsletter/addtype">Create</a></li>
<li class="action" id="deletedrafts"><a href="admin/newsletter/deletedrafts">Delete</a></li>
</ul>
<div style="clear:both;"></div>
<form class="actionparams" id="addtype_options" style="display: none" action="admin/newsletter/addtype">
<input type="hidden" name="ParentID" value="" />
<select name="PageType" id="add_type">
<option value="type">Add new type</option>
<option value="draft">Add new draft</option>
</select>
<input class="action" type="submit" value="Go" />
</form>
<form class="actionparams" id="deletedrafts_options" style="display: none" action="admin/newsletter/remove">
<p>Select the drafts that you want to delete and then click the button below</p>
<input type="hidden" name="csvIDs" />
<input type="submit" value="Delete the selected drafts" />
</form>
<% include NewsletterAdmin_SiteTree %>
</div>
</div>

View File

@ -0,0 +1,32 @@
<div class="title"><div>Edit Content</div></div>
<% include Editor_toolbar %>
<div id="form_actions_right" class="ajaxActions">
</div>
<form class="actionparams" id="action_send_options" style="display:none" action="admin/newsletter/sendnewsletter">
<fieldset>
<input type="hidden" name="NewsletterID" />
<ul class="optionset">
<li><input name="SendType" id="SendTypeTest" value="Test" checked="checked" type="radio" /> Send test to <input name="TestEmail" type="text" /></li>
<li><input name="SendType" id="SendTypeList" value="List" type="radio" /> Send to the mailing list</li>
</ul>
$SendProgressBar
</fieldset>
<p class="actions">
<input type="submit" value="Send newsletter" />
</p>
</form>
<% if EditForm %>
$EditForm
<% else %>
<form id="Form_EditForm" action="admin/newsletter?executeForm=EditForm" method="post" enctype="multipart/form-data">
<p>Welcome to the $ApplicationName newsletter admininistration section. Please choose a folder from the left.</p>
</form>
<% end_if %>
<p id="statusMessage" style="visibility:hidden"></p>

View File

@ -0,0 +1,14 @@
<% if Reports %>
<ul id="sitetree" class="tree unformatted">
<li id="$ID" class="root"><a>Reports</a>
<ul>
<% control Reports %>
<li id="$ID">
<a href="$baseURL/admin/showreport/$ID" title="$TreeDescription">$TreeTitle</a>
</li>
<% end_control %>
</ul>
</li>
</ul>
<% end_if %>

View File

@ -0,0 +1,17 @@
<style>
ul.tree a{
background-image: url(cms/images/treeicons/reports-file.png);
}
</style>
<div class="title"><div>Reports</div></div>
<div id="treepanes">
<div id="sitetree_holder">
<% include ReportAdmin_SiteTree %>
</div>
</div>

View File

@ -0,0 +1,11 @@
<div class="title"><div>Report Details</div></div>
<% if EditForm %>
$EditForm
<% else %>
<form id="Form_EditForm" action="admin/reports?executeForm=EditForm" method="post" enctype="multipart/form-data">
<p>Welcome to the $ApplicationName reporting section. Please choose a specific report from the left.</p>
</form>
<% end_if %>
<p id="statusMessage" style="visibility:hidden"></p>

View File

@ -0,0 +1,28 @@
<div class="title"><div>Security Groups</div></div>
<div id="treepanes">
<div id="sitetree_holder">
<ul id="TreeActions">
<li class="action" id="addgroup"><a href="admin/security/addgroup">Create</a></li>
<li class="action" id="deletegroup"><a href="admin/security/deletegroup">Delete</a></li>
<li class="action" id="sortitems"><a href="#">Reorganise</a></li>
</ul>
<div style="clear:both;"></div>
<form class="actionparams" id="addgroup_options" style="display: none" action="admin/security/addgroup">
<input type="hidden" name="ParentID" />
<input class="action" type="submit" value="Go" />
</form>
<form class="actionparams" id="deletegroup_options" style="display: none" action="admin/security/deleteitems">
<p>Select the pages that you want to delete and then click the button below</p>
<input type="hidden" name="csvIDs" />
<input type="submit" value="Delete the selected groups" />
</form>
<form class="actionparams" id="sortitems_options" style="display: none" action="">
<p id="sortitems_message" style="margin: 0">To reorganise your site, drag the pages around as desired.</p>
</form>
$SiteTreeAsUL
</div>
</div>

View File

@ -0,0 +1,14 @@
<div class="title"><div>Users in this Group</div></div>
<div id="form_actions_right" class="ajaxActions">
</div>
<% if EditForm %>
$EditForm
<% else %>
<form id="Form_EditForm" action="admin/security?executeForm=EditForm" method="post" enctype="multipart/form-data">
<p>Welcome to the $ApplicationName security admininistration section. Please choose a group from the left.</p>
</form>
<% end_if %>
<p id="statusMessage" style="visibility:hidden"></p>

72
templates/LeftAndMain.ss Normal file
View File

@ -0,0 +1,72 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<% base_tag %>
<title>$ApplicationName</title>
<link rel="stylesheet" type="text/css" href="cms/css/typography.css" />
<link rel="stylesheet" type="text/css" href="cms/css/layout.css" />
<link rel="stylesheet" type="text/css" href="cms/css/cms_left.css" />
<link rel="stylesheet" type="text/css" href="cms/css/cms_right.css" />
</head>
<body class="stillLoading">
<div id="Loading" style="background: #FFF url(cms/images/loading.gif) 50% 50% no-repeat; position: absolute;z-index: 100000;height: 100%;width: 100%;margin: 0;padding: 0;z-index: 100000;position: absolute;">Loading...</div>
<div id="top">
<div id="Logo" style="$LogoStyle">
<% if ApplicationLogoText %>
<a href="http://www.silverstripe.com/">$ApplicationLogoText</a><br />
<% end_if %>
</div>
<ul id="MainMenu">
<% control MainMenu %>
<li class="$LinkingMode" id="Menu-$Code"><a href="$Link">$Title</a></li>
<% end_control %>
</ul>
</div><div id="left" style="float:left">
$Left
</div>
<div id="separator" style="float:left">
&nbsp;
</div>
<div class="right" id="right">
$Right
</div>
<% if RightBottom %>
<div class="right" id="rightbottom">
$RightBottom
</div>
<% end_if %>
<div id="bottom">
<div class="holder">
<div id="logInStatus">
<a href="http://www.silverstripe.com" title="Silverstripe Website">Silverstripe CMS</a>&nbsp;-&nbsp;
<abbr style="border-style: none" title="This is the $ApplicationName version that you are currently running, technically it's the CVS branch">$CMSVersion</abbr> &nbsp; &nbsp; &nbsp;
<% control CurrentMember %>
Logged in as $FirstName $Surname - <a href="Security/logout">log out</a>
<% end_control %>
</div>
<div id="switchView" class="bottomTabs">
<% if class = CMSMain %>
<div class="blank"> View page in: </div>
<% else %>
<div class="blank"> Switch to: </div>
<% end_if %>
<a class="current">CMS</a>
<a id="viewStageSite" href="home/?stage=Stage" style="left : -1px;">Draft Site</a>
<a id="viewLiveSite" href="home/?stage=Live" style="left : -3px;">Published Site</a>
<a style="display: none;left : -5px;" id="viewArchivedSite" href="home/">Archived Site</a>
</div>
</div>
</div>
<script type="text/javascript">Behaviour.addLoader(hideLoading)</script>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<% base_tag %>
<title>SilverStripe CMS - $Title</title>
</head>
<body onblur="window.close()">
$PrintForm
</body>
</html>

17
templates/MemberList.ss Executable file
View File

@ -0,0 +1,17 @@
<div id="MemberListField">
<div class="MemberListFilter">
<a class="showhide closed" href="#">Filter</a>
<div id="MemberListFilterControls">
<input id="MemberListBaseGroupID" type="hidden" name="MemberListBaseGroup" value="$GroupID" />
<input id="MemberListDontShowPassword" type="hidden" name="MemberListDontShowPassword" value="$DontShowPassword" />
$SearchField
$OrderByField
$GroupFilter
<input id="MemberListFilterButton" type="submit" value="Filter" name="filter" />
</div>
</div>
<div id="MemberList">
<% include MemberList_Table %>
</div>
</div>

19
templates/NewsletterList.ss Executable file
View File

@ -0,0 +1,19 @@
<table id="{$Status}List" class="CMSList NewsletterList">
<thead>
<tr>
<td class="Email">Subject</td>
<td class="Surname">Content</td>
</tr>
</thead>
<tbody>
<% control Newsletters %>
<tr id="letter-$ID">
<td>$Subject</td>
<td>$Content</td>
</tr>
<% end_control %>
<!-- $AddRecordForm.AsTableRow -->
</tbody>
</table>

View File

@ -0,0 +1,48 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<% base_tag %>
$MetaTags
<style type="text/css" media="screen">
@import "mot/css/css.css";
@import "mot/css/typography.css";
</style>
<style type="text/css" media="print">
@import "mot/css/print.css";
</style>
<style type="text/css">
body {
padding-top: 1em;
}
</style>
<script src="jsparty/prototype.js" type="text/javascript"></script>
<script src="jsparty/behaviour.js" type="text/javascript"></script>
<script src="cms/javascript/Newsletter_UploadForm.js" type="text/javascript"></script>
<% if ImportMessage %>
<script type="text/javascript">
top.statusMessage('Imported new members','good');
top.reloadRecipientsList();
</script>
<% end_if %>
</head>
<body>
<% if ImportMessage %>
<p>
$ImportMessage
<ul>
<li><label>New members imported:</label>$NewMembers</li>
<li><label>Members updated:</label>$ChangedMembers</li>
<li><label>Number of details changed:</label>$ChangedFields</li>
<li><label>Records skipped:</label>$SkippedRecords</li>
<li><label>Time taken: </label>$Time seconds</li>
</ul>
</p>
<% end_if %>
<% if ErrorMessage %>
<p class="message bad">
$ErrorMessage
</p>
<% end_if %>
$UploadForm
</body>
</html>

View File

@ -0,0 +1,46 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<% base_tag %>
$MetaTags
<style type="text/css" media="screen">
@import "mot/css/css.css";
@import "mot/css/typography.css";
</style>
<style type="text/css" media="print">
@import "mot/css/print.css";
</style>
<script src="jsparty/prototype.js" type="text/javascript"></script>
<script src="jsparty/behaviour.js" type="text/javascript"></script>
<script src="cms/javascript/Newsletter_UploadForm.js" type="text/javascript"></script>
</head>
<body onload="">
<h1>Contents of $FileName</h1>
<form method="post" action="admin/newsletter/?executeForm=UploadForm" name="UploadForm">
<% control CustomSetFields %>
$FieldHolder
<% end_control %>
<input type="submit" name="action_confirm" value="Confirm" />
<input type="submit" name="action_cancel" value="Cancel" />
<input type="hidden" name="ID" value="$TypeID" />
<table summary="Recipients imported from $FileName">
<tbody>
<tr>
<% control ColumnHeaders %>
<th>
$Field
</th>
<% end_control %>
</tr>
<% control Rows %>
<tr>
<% control Cells %>
<td>$Value</td>
<% end_control %>
</tr>
<% end_control %>
</tbody>
</table>
</form>
</body>
</html>

View File

@ -0,0 +1,45 @@
<div id="PageComments_holder" class="typography">
<h4>Post your comment</h4>
$PostCommentForm
<h4>Comments</h4>
<div id="CommentHolder">
<% if Comments %>
<ul id="PageComments">
<% control Comments %>
<li class="$EvenOdd<% if FirstLast %> $FirstLast <% end_if %> $SpamClass">
<% include PageCommentInterface_singlecomment %>
</li>
<% end_control %>
</ul>
<% if Comments.MoreThanOnePage %>
<div id="PageCommentsPagination">
<% if Comments.PrevLink %>
<a href="$Comments.PrevLink">&laquo; previous</a>
<% end_if %>
<% if Comments.Pages %>
<% control Comments.Pages %>
<% if CurrentBool %>
<strong>$PageNum</strong>
<% else %>
<a href="$Link">$PageNum</a>
<% end_if %>
<% end_control %>
<% end_if %>
<% if Comments.NextLink %>
<a href="$Comments.NextLink">next &raquo;</a>
<% end_if %>
</div>
<% end_if %>
<% else %>
<p id="NoComments">No one has commented on this page yet.</p>
<% end_if %>
</div>
</div>

View File

@ -0,0 +1,16 @@
<p class="comment">$Comment.XML</p>
<p class="info">
<span>Posted by $Name, $Created.Nice ($Created.Ago)</span>
<br />
<span>
<% if DeleteLink %>
<a href="$DeleteLink" class="deletelink">remove this comment</a>
<% end_if %>
<% if SpamLink %>
<a href="$SpamLink" class="spamlink">this comment is spam</a>
<% end_if %>
<% if HamLink %>
<a href="$HamLink" class="hamlink">this comment is not spam</a>
<% end_if %>
</span>
</p>

View File

@ -0,0 +1 @@
$OnCompleteMessage

10
templates/TaskList.ss Executable file
View File

@ -0,0 +1,10 @@
<p>$Message</p>
<ul id="TaskList">
<% control Tasks %>
<li class="$EvenOdd">
<a href="admin/show/$ID">$Title</a>
<div class="extra">by $RequestedBy.FirstName&nbsp;$RequestedBy.Surname.Initial, $Created.Ago</div>
</li>
<% end_control %>
</ul>

View File

@ -0,0 +1,3 @@
<div id="$Name" class="thumbnailstrip parent=$ParentField updatemethod=$updateMethod">
<span style="float: left">(Choose a folder above)</span>
</div>

10
templates/WaitingOn.ss Executable file
View File

@ -0,0 +1,10 @@
<p>$Message</p>
<ul id="TaskList">
<% control Tasks %>
<li class="$EvenOdd">
<a href="admin/show/$ID">$Title</a>
<div class="extra">assigned to $AssignedTo.FirstName&nbsp;$AssignedTo.Surname.Initial $LastEdited.Ago</div>
</li>
<% end_control %>
</ul>

View File

@ -0,0 +1,20 @@
<html>
<head>
</head>
<body>
<h1>$Subject</h1>
<p>
The following data was submitted to your website:
</p>
$Body
<table>
<% control Fields %>
<tr>
<td><b>$Title</b></td>
<td>$Value</td>
</tr>
<% end_control %>
</table>
</body>
</html>

View File

@ -0,0 +1,3 @@
You have asked to view the content of our site on $ArchiveDate.Date
You can access the archived site at this link: $ArchiveURL