MINOR Initial commit

This commit is contained in:
Ingo Schommer 2012-04-18 23:15:45 +02:00
commit 552a5006da
17 changed files with 1454 additions and 0 deletions

24
LICENSE Normal file
View File

@ -0,0 +1,24 @@
* Copyright (c) 2011, SilverStripe Ltd
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the <organization> nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY SilverStripe Ltd. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Silverstripe Ltd. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

329
README.md Normal file
View File

@ -0,0 +1,329 @@
# Widgets Module
## Introduction
[Widgets](http://silverstripe.org/widgets) are small pieces of functionality such as showing the latest Comments or Flickr Photos. They normally display on
the sidebar of your website. To check out a what a [Widget](http://silverstripe.org/widgets) can do watch the
[Widget video](http://silverstripe.com/assets/screencasts/SilverStripe-Blog-DragDrop-Widgets.swf) and try out the
[demo site](http://demo.silverstripe.org/)
## Requirements
* SilverStripe 3.0
## How to Use A Widget
### Downloading and Contributing Widgets
* To download widgets visit [Widgets section](http://silverstripe.org/widgets)
* Upload widgets you want to share to
[http://silverstripe.org/widgets/manage/add](http://silverstripe.org/widgets/manage/add). Make sure you read the
packaging instructions at the bottom of the page about how to make your widget package.
### Installing a widget
By following the "Packaging" rules below, widgets are easily installed.
* Install the [blog module](http://www.silverstripe.org/blog-module/) (by default only the Blog has widgets enabled)
* Download the file and unzip to the main folder of your SilverStripe website, e.g. to `/widget_<widget-name>/`. The folder
will contain a few files, which generally won't need editing or reading.
* Run `http://my-website.com/dev/build`
* Login to the CMS and go to the 'Blog' page. Choose the "widgets" tab and drag n drop the new widget to activate it.
* Your blog will now have the widget shown
### Adding widgets to other pages
You have to do a couple things to get a Widget to work on a page.
First step is to add an WidgetArea to the Database to store the widget details. Then you have to edit the CMS to add a
Widget Form to manage the widgets. An example of this is below
**mysite/code/Page.php**
class Page extends SiteTree {
...
static $has_one = array(
"Sidebar" => "WidgetArea",
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab("Root.Content.Widgets", new WidgetAreaEditor("Sidebar"));
return $fields;
}
....
}
Then in your Template you need to call $SideBar wherever you want to render the widget
For example: using the blackcandy theme I put this piece of code above the closing `</div>`
**themes/blackcandy/templates/Includes/Sidebar.ss**
$Sidebar
## Writing your own widgets
To create a Widget you need at least three files - a php file containing the class, a template file of the same name and
a config file called *_config.php* (if you dont need any config options for the widget to work then you can make it
blank). Each widget should be in its own folder like widgets_widgetName/
After installing or creating a new widget, **make sure to run db/build?flush=1** at the end of the URL, *before*
attempting to use it.
The class should extend the Widget class, and must specify three static variables - $title, the title that will appear
in the rendered widget (eg Photos), $cmsTitle, a more descriptive title that will appear in the cms editor (eg Flickr
Photos), and $description, a short description that will appear in the cms editor (eg This widget shows photos from
Flickr). The class may also specify functions to be used in the template like a page type can.
If a Widget has configurable options, then it can specify a number of database fields to store these options in via the
static $db array, and also specify a getCMSFields function that returns a !FieldList, much the same way as a page type
does.
An example widget is below:
**FlickrWidget.php**
<?php
class FlickrWidget extends Widget {
static $db = array(
"User" => "Varchar",
"Photoset" => "Varchar",
"Tags" => "Varchar",
"NumberToShow" => "Int"
);
static $defaults = array(
"NumberToShow" => 8
);
static $title = "Photos";
static $cmsTitle = "Flickr Photos";
static $description = "Shows flickr photos.";
public function Photos() {
Requirements::javascript(THIRDPARTY_DIR . "/prototype/prototype.js");
Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/effects.js");
Requirements::javascript("mashups/javascript/lightbox.js");
Requirements::css("mashups/css/lightbox.css");
$flickr = new FlickrService();
if($this->Photoset == "") {
$photos = $flickr->getPhotos($this->Tags, $this->User, $this->NumberToShow, 1);
} else {
$photos = $flickr->getPhotoSet($this->Photoset, $this->User, $this->NumberToShow, 1);
}
$output = new DataObjectSet();
foreach($photos->PhotoItems as $photo) {
$output->push(new ArrayData(array(
"Title" => $photo->title,
"Link" => "http://farm1.static.flickr.com/" . $photo->image_path .".jpg",
"Image" => "http://farm1.static.flickr.com/" .$photo->image_path. "_s.jpg"
)));
}
return $output;
}
public function getCMSFields() {
return new FieldList(
new TextField("User", "User"),
new TextField("PhotoSet", "Photo Set"),
new TextField("Tags", "Tags"),
new NumericField("NumberToShow", "Number to Show")
);
}
}
?>
**FlickrWidget.ss**
<% control Photos %>
<a href="$Link" rel="lightbox" title="$Title"><img src="$Image" alt="$Title" /></a>
<% end_control %>
## Extending and Customizing
### Rendering a $Widget Individually
To call a single Widget in a page - without adding a widget area in the CMS for you to add / delete the widgets, you can
define a merge variable in the Page Controller and include it in the Page Template.
This example creates an RSSWidget with the SilverStripe blog feed.
<?php
public function SilverStripeFeed() {
$widget = new RSSWidget();
$widget->RssUrl = "http://feeds.feedburner.com/silverstripe-blog";
return $widget->renderWith("WidgetHolder");
}
?>
To render the widget, simply include $SilverStripeFeed in your template:
$SilverStripeFeed
As directed in the definition of SilverStripeFeed(), the Widget will be rendered through the WidgetHolder template. This
is pre-defined at `framework/templates/WidgetHolder.ss` and simply consists of:
<div class="WidgetHolder">
<h3>$Title</h3>
$Content
</div>
You can override the WidgetHolder.ss and Widget.ss templates in your theme too by adding WidgetHolder and Widget
templates to `themes/myThemeName/templates/Includes/`
### Changing the title of your widget
To change the title of your widget, you need to override the Title() method. By default, this simply returns the $title
variable. For example, to set your widgets title to 'Hello World!', you could use:
**widgets_yourWidget/YourWidgetWidget.php**
public function Title() {
return "Hello World!";
}
but, you can do exactly the same by setting your $title variable.
A more common reason for overriding Title() is to allow the title to be set in the CMS. Say you had a text field in your
widget called WidgetTitle, that you wish to use as your title. If nothing is set, then you'll use your default title.
This is similar to the RSS Widget in the blog module.
public function Title() {
return $this->WidgetTitle ? $this->WidgetTitle : self::$title;
}
This returns the value inputted in the CMS, if it's set or what is in the $title variable if it isn't.
### Forms within Widgets
To implement a form inside a widget, you need to implement a custom controller for your widget to return this form. Make
sure that your controller follows the usual naming conventions, and it will be automatically picked up by the
`WidgetArea` rendering in your *Page.ss* template.
**mysite/code/MyWidget.php**
class MyWidget extends Widget {
static $db = array(
'TestValue' => 'Text'
);
}
class MyWidget_Controller extends Widget_Controller {
public function MyFormName() {
return new Form(
$this,
'MyFormName',
new FieldList(
new TextField('TestValue')
),
new FieldList(
new FormAction('doAction')
)
);
}
public function doAction($data, $form) {
// $this->widget points to the widget
}
}
To output this form, modify your widget template.
**mysite/templates/MyWidget.ss**
$Content
$MyFormName
**Note:** The necessary controller actions are only present in subclasses of `Page_Controller`. To use
widget forms in other controller subclasses, have a look at *ContentController->handleWidget()* and
*ContentController::$url_handlers*.
## But what if I have widgets on my blog currently??
If you currently have a blog installed, the widget fields are going to double up on those pages (as the blog extends the
Page class). One way to fix this is to comment out line 30 in BlogHolder.php and remove the DB entry by running a
`http://www.mysite.com/db/build`.
**blog/code/BlogHolder.php**
<?php
class BlogHolder extends Page {
........
static $has_one = array(
// "SideBar" => "WidgetArea", COMMENT OUT
'Newsletter' => 'NewsletterType'
.......
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeFieldFromTab("Root.Content","Content");
// $fields->addFieldToTab("Root.Content.Widgets", new WidgetAreaEditor("SideBar")); COMMENT OUT
........
Then you can use the Widget area you defined on Page.php
## Releasing Your Widget
### Packaging
For a widget to be put in our official widget database they must follow this convention - If the name of your widget was
"YourName" then:
#### File Structure for your widget
You should have a folder called widget_YourName in the top level (the one with framework, cms..) with all your files. See
the example below. Your widget **MUST** have at least 1 Template file, 1 PHP file, the README File
[(Example)](http://open.silverstripe.com/browser/modules/widgets/twitter/trunk/README)and an _config.php file for
configuration. If you dont need any config options for the widget to work then you still need an _config.php by you can
make it blank
The decision over whether to configure a widget in _config.php or in the CMS is important:
* If the setting is the kind of thing that a website author, familiar with common business apps such as Word and
Outlook, would understand - then make it configurable in the CMS.
* If the setting is the kind of thing that the person setting up the website - doing the design and/or development -
would understand, then make it configurable in the _config.php file.
This way, the CMS remains an application designed for content authors, and not developers.
*widget_name/_config.php*
<?php /* */ ?>
**Example Widget Structure**
![](_images/widget_demo.gif)
#### How to make the Package
* Make a tar.gz file called widgets_YourName-0.1.tar.gz (where 0.1 is the version number).
* Ensure when you "unzip" the compressed file it has everything the "widgets_YourName" folder with everything inside
it.
* If made official, it will be given these locations at silverstripe.com:
* SVN location: http://svn.silverstripe.com/open/modules/widgets/flickr/trunk
* Official download: http://www.silverstripe.com/assets/downloads/widgets/widgets_flickr-0.1.1.tar.gz

0
_config.php Normal file
View File

View File

@ -0,0 +1,142 @@
<?php
/**
* Special field type for selecting and configuring widgets on a page.
* @package cms
* @subpackage content
*/
class WidgetAreaEditor extends FormField {
/**
* 3 variables to hold titles for the template
*/
public $InUseTitle;
public $AvailableTitle;
public $ToAddTitle;
function __construct($name, $widgetClasses = array('Widget'), $maxWidgets = 0) {
$this->MaxWidgets = $maxWidgets;
$this->widgetClasses = $widgetClasses;
parent::__construct($name);
}
function FieldHolder($properties = array()) {
Requirements::css('widgets/css/WidgetAreaEditor.css');
Requirements::javascript(THIRDPARTY_DIR . "/prototype/prototype.js");
Requirements::javascript(THIRDPARTY_DIR . '/behaviour/behaviour.js');
Requirements::javascript('widgets/javascript/WidgetAreaEditor.js');
return $this->renderWith("WidgetAreaEditor");
}
function AvailableWidgets() {
$widgets= new ArrayList();
foreach($this->widgetClasses as $widgetClass) {
$classes = ClassInfo::subclassesFor($widgetClass);
array_shift($classes);
foreach($classes as $class) {
$widgets->push(singleton($class));
}
}
return $widgets;
}
function UsedWidgets() {
// Call class_exists() to load Widget.php earlier and avoid a segfault
class_exists('Widget');
$relationName = $this->name;
$widgets = $this->form->getRecord()->getComponent($relationName)->Items();
return $widgets;
}
function IdxField() {
return $this->id() . 'ID';
}
function Value() {
$relationName = $this->name;
return $this->form->getRecord()->getComponent($relationName)->ID;
}
function saveInto(DataObjectInterface $record) {
$name = $this->name;
$idName = $name . "ID";
$widgetarea = $record->getComponent($name);
$widgetarea->write();
$record->$idName = $widgetarea->ID;
$widgets = $widgetarea->Items();
// store the field IDs and delete the missing fields
// alternatively, we could delete all the fields and re add them
$missingWidgets = array();
if($widgets) {
foreach($widgets as $existingWidget) {
$missingWidgets[$existingWidget->ID] = $existingWidget;
}
}
if(isset($_REQUEST['Widget'])) {
foreach(array_keys($_REQUEST['Widget']) as $widgetAreaName) {
if ($widgetAreaName !== $this->name) {
continue;
}
foreach(array_keys($_REQUEST['Widget'][$widgetAreaName]) as $newWidgetID) {
$newWidgetData = $_REQUEST['Widget'][$widgetAreaName][$newWidgetID];
// Sometimes the id is "new-1" or similar, ensure this doesn't get into the query
if(!is_numeric($newWidgetID)) {
$newWidgetID = 0;
}
// \"ParentID\" = '0' is for the new page
$widget = DataObject::get_one(
'Widget',
"(\"ParentID\" = '{$record->$name()->ID}' OR \"ParentID\" = '0') AND \"Widget\".\"ID\" = '$newWidgetID'"
);
// check if we are updating an existing widget
if($widget && isset($missingWidgets[$widget->ID])) {
unset($missingWidgets[$widget->ID]);
}
// create a new object
if(!$widget && !empty($newWidgetData['Type']) && class_exists($newWidgetData['Type'])) {
$widget = new $newWidgetData['Type']();
$widget->ID = 0;
$widget->ParentID = $record->$name()->ID;
if(!is_subclass_of($widget, 'Widget')) {
$widget = null;
}
}
if($widget) {
if($widget->ParentID == 0) {
$widget->ParentID = $record->$name()->ID;
}
// echo "Saving $widget->ID into $name/$widget->ParentID\n<br/>";
$widget->populateFromPostData($newWidgetData);
}
}
}
}
// remove the fields not saved
if($missingWidgets) {
foreach($missingWidgets as $removedWidget) {
if(isset($removedWidget) && is_numeric($removedWidget->ID)) {
$removedWidget->delete();
}
}
}
}
}

232
code/model/Widget.php Normal file
View File

@ -0,0 +1,232 @@
<?php
/**
* Widgets let CMS authors drag and drop small pieces of functionality into
* defined areas of their websites.
*
* ## Forms
* You can use forms in widgets by implementing a {@link Widget_Controller}.
* See {@link Widget_Controller} for more information.
*
* @package cms
* @subpackage widgets
*/
class Widget extends DataObject {
static $db = array(
"Sort" => "Int",
"Enabled" => "Boolean"
);
static $defaults = array(
'Enabled' => true
);
static $has_one = array(
"Parent" => "WidgetArea",
);
static $has_many = array();
static $many_many = array();
static $belongs_many_many = array();
static $default_sort = "\"Sort\"";
static $title = "Widget Title";
static $cmsTitle = "Name of this widget";
static $description = "Description of what this widget does.";
function getCMSFields() {
$fields = new FieldList();
$this->extend('updateCMSFields', $fields);
return $fields;
}
/**
* Note: Overloaded in {@link Widget_Controller}.
*
* @return string HTML
*/
function WidgetHolder() {
return $this->renderWith("WidgetHolder");
}
/**
* Renders the widget content in a custom template with the same name as the current class.
* This should be the main point of output customization.
*
* Invoked from within WidgetHolder.ss, which contains
* the "framing" around the custom content, like a title.
*
* Note: Overloaded in {@link Widget_Controller}.
*
* @return string HTML
*/
function Content() {
return $this->renderWith(array_reverse(ClassInfo::ancestry($this->class)));
}
function Title() {
return Object::get_static($this->class, 'title');
}
function CMSTitle() {
return Object::get_static($this->class, 'cmsTitle');
}
function Description() {
return Object::get_static($this->class, 'description');
}
function DescriptionSegment() {
return $this->renderWith('WidgetDescription');
}
/**
* @see Widget_Controller->editablesegment()
*/
function EditableSegment() {
return $this->renderWith('WidgetEditor');
}
function CMSEditor() {
$output = '';
$fields = $this->getCMSFields();
foreach($fields as $field) {
$name = $field->Name();
$field->setValue($this->getField($name));
$renderedField = $field->FieldHolder();
$renderedField = preg_replace("/name=\"([A-Za-z0-9\-_]+)\"/", "name=\"Widget[" . $this->ID . "][\\1]\"", $renderedField);
$renderedField = preg_replace("/id=\"([A-Za-z0-9\-_]+)\"/", "id=\"Widget[" . $this->ID . "][\\1]\"", $renderedField);
$output .= $renderedField;
}
return $output;
}
function ClassName() {
return $this->class;
}
function Name() {
return "Widget[".$this->ID."]";
}
function populateFromPostData($data) {
foreach($data as $name => $value) {
if($name != "Type") {
$this->setField($name, $value);
}
}
$this->write();
// The field must be written to ensure a unique ID.
$this->Name = $this->class.$this->ID;
$this->write();
}
}
/**
* Optional controller for every widget which has its own logic,
* e.g. in forms. It always handles a single widget, usually passed
* in as a database identifier through the controller URL.
* Needs to be constructed as a nested controller
* within a {@link ContentController}.
*
* ## Forms
* You can add forms like in any other SilverStripe controller.
* If you need access to the widget from within a form,
* you can use `$this->controller->getWidget()` inside the form logic.
* Note: Widget controllers currently only work on {@link Page} objects,
* because the logic is implemented in {@link ContentController->handleWidget()}.
* Copy this logic and the URL rules to enable it for other controllers.
*
* @package cms
* @subpackage widgets
*/
class Widget_Controller extends Controller {
/**
* @var Widget
*/
protected $widget;
static $allowed_actions = array(
'editablesegment'
);
function __construct($widget = null) {
// TODO This shouldn't be optional, is only necessary for editablesegment()
if($widget) {
$this->widget = $widget;
$this->failover = $widget;
}
parent::__construct();
}
public function Link($action = null) {
$segment = Controller::join_links('widget', ($this->widget ? $this->widget->ID : null), $action);
if(Director::get_current_page()) {
return Director::get_current_page()->Link($segment);
} else {
return Controller::curr()->Link($segment);
}
}
/**
* @return Widget
*/
function getWidget() {
return $this->widget;
}
/**
* Overloaded from {@link Widget->Content()}
* to allow for controller/form linking.
*
* @return string HTML
*/
function Content() {
return $this->renderWith(array_reverse(ClassInfo::ancestry($this->widget->class)));
}
/**
* Overloaded from {@link Widget->WidgetHolder()}
* to allow for controller/form linking.
*
* @return string HTML
*/
function WidgetHolder() {
return $this->renderWith("WidgetHolder");
}
/**
* Uses the `WidgetEditor.ss` template and {@link Widget->editablesegment()}
* to render a administrator-view of the widget. It is assumed that this
* view contains form elements which are submitted and saved through {@link WidgetAreaEditor}
* within the CMS interface.
*
* @return string HTML
*/
function editablesegment() {
$className = $this->urlParams['ID'];
if(class_exists($className) && is_subclass_of($className, 'Widget')) {
$obj = new $className();
return $obj->EditableSegment();
} else {
user_error("Bad widget class: $className", E_USER_WARNING);
return "Bad widget class name given";
}
}
}
/**
* @package cms
* @subpackage widgets
*/
class Widget_TreeDropdownField extends TreeDropdownField {
function FieldHolder($properties = array()) {}
function Field($properties = array()) {}
}

72
code/model/WidgetArea.php Normal file
View File

@ -0,0 +1,72 @@
<?php
/**
* Represents a set of widgets shown on a page.
* @package cms
* @subpackage widgets
*/
class WidgetArea extends DataObject {
static $db = array();
static $has_one = array();
static $has_many = array(
"Widgets" => "Widget"
);
static $many_many = array();
static $belongs_many_many = array();
public $template = __CLASS__;
/**
* Used in template instead of {@link Widgets()}
* to wrap each widget in its controller, making
* it easier to access and process form logic
* and actions stored in {@link Widget_Controller}.
*
* @return SS_List Collection of {@link Widget_Controller}
*/
function WidgetControllers() {
$controllers = new ArrayList();
foreach($this->ItemsToRender() as $widget) {
// find controller
$controllerClass = '';
foreach(array_reverse(ClassInfo::ancestry($widget->class)) as $widgetClass) {
$controllerClass = "{$widgetClass}_Controller";
if(class_exists($controllerClass)) break;
}
$controller = new $controllerClass($widget);
$controller->init();
$controllers->push($controller);
}
return $controllers;
}
function Items() {
return $this->getComponents('Widgets');
}
function ItemsToRender() {
return $this->getComponents('Widgets', "\"Widget\".\"Enabled\" = 1");
}
function forTemplate() {
return $this->renderWith($this->template);
}
function setTemplate($template) {
$this->template = $template;
}
function onBeforeDelete() {
parent::onBeforeDelete();
foreach($this->Widgets() as $widget) {
$widget->delete();
}
}
}

3
templates/WidgetArea.ss Normal file
View File

@ -0,0 +1,3 @@
<% control WidgetControllers %>
$WidgetHolder
<% end_control %>

View File

@ -0,0 +1,32 @@
<div class="WidgetAreaEditor" id="WidgetAreaEditor-$Name" name="$Name"<% if MaxWidgets %> maxwidgets="$MaxWidgets"<% end_if %>>
<input type="hidden" id="$Name" name="$IdxField" value="$Value" />
<div class="availableWidgetsHolder">
<h2><% _t('AVAILABLE', 'Available Widgets') %></h2>
<p><% _t('AVAILWIDGETS', 'Click a widget title below to use it on this page.') %></p>
<div class="availableWidgets" id="availableWidgets-$Name">
<% if AvailableWidgets %>
<% control AvailableWidgets %>
$DescriptionSegment
<% end_control %>
<% else %>
<div class="NoWidgets" id="NoWidgets-$Name">
<p><% _t('NOAVAIL', 'There are currently no widgets available.') %></p>
</div>
<% end_if %>
</div>
</div>
<div class="usedWidgetsHolder">
<h2><% _t('INUSE', 'Widgets currently used') %></h2>
<p><% _t('TOSORT', 'To sort currently used widgets on this page, drag them up and down.') %></p>
<div class="usedWidgets" id="usedWidgets-$Name">
<% if UsedWidgets %>
<% control UsedWidgets %>
$EditableSegment
<% end_control %>
<% else %>
<div class="NoWidgets" id="NoWidgets-$Name"></div>
<% end_if %>
</div>
</div>
</div>

View File

@ -0,0 +1,6 @@
<div class="Widget" id="$ClassName">
<h3 title="<% _t('CLICKTOADDWIDGET', 'Click to add this widget') %>">$CMSTitle</h3>
<div class="widgetDescription">
<p>$Description</p>
</div>
</div>

12
templates/WidgetEditor.ss Normal file
View File

@ -0,0 +1,12 @@
<div class="$ClassName Widget" id="$Name">
<h3 class="handle">$CMSTitle</h3>
<div class="widgetDescription">
<p>$Description</p>
</div>
<div class="widgetFields">
$CMSEditor
<input type="hidden" name="$Name[Type]" value="$ClassName" />
<input type="hidden" name="$Name[Sort]" value="$Sort" />
</div>
<p class="deleteWidget"><span class="widgetDelete"><% _t('DELETE', 'Delete') %></span></p>
</div>

View File

@ -0,0 +1,4 @@
<div class="WidgetHolder $ClassName<% if FirstLast %> $FirstLast<% end_if %>">
<% if Title %><h3>$Title</h3><% end_if %>
$Content
</div>

View File

@ -0,0 +1,471 @@
<?php
/**
* @package cms
* @subpackage tests
*/
class WidgetAreaEditorTest extends SapphireTest {
/**
* This is the widget you want to use for your unit tests.
*/
protected $widgetToTest = 'WidgetAreaEditorTest_TestWidget';
protected $extraDataObjects = array(
'WidgetAreaEditorTest_FakePage',
'WidgetAreaEditorTest_TestWidget',
);
protected $usesDatabase = true;
function testFillingOneArea() {
$oldRequest = $_REQUEST;
$_REQUEST = array(
'Widget' => array(
'BottomBar' => array(
'new-1' => array(
'Title' => 'MyTestWidget',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$page = new WidgetAreaEditorTest_FakePage();
$editorSide->saveInto($page);
$editorBott->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
$this->assertEquals($page->BottomBar()->Widgets()->Count(), 1);
$this->assertEquals($page->SideBar()->Widgets()->Count(), 0);
$_REQUEST = $oldRequest;
}
function testFillingTwoAreas() {
$oldRequest = $_REQUEST;
$_REQUEST = array(
'Widget' => array(
'SideBar' => array(
'new-1' => array(
'Title' => 'MyTestWidgetSide',
'Type' => $this->widgetToTest,
'Sort' => 0
)
),
'BottomBar' => array(
'new-1' => array(
'Title' => 'MyTestWidgetBottom',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$page = new WidgetAreaEditorTest_FakePage();
$editorSide->saveInto($page);
$editorBott->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
// Make sure they both got saved
$this->assertEquals($page->BottomBar()->Widgets()->Count(), 1);
$this->assertEquals($page->SideBar()->Widgets()->Count(), 1);
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
$this->assertEquals($sideWidgets[0]->Title(), 'MyTestWidgetSide');
$this->assertEquals($bottWidgets[0]->Title(), 'MyTestWidgetBottom');
$_REQUEST = $oldRequest;
}
function testDeletingOneWidgetFromOneArea() {
$oldRequest = $_REQUEST;
// First get some widgets in there
$_REQUEST = array(
'Widget' => array(
'SideBar' => array(
'new-1' => array(
'Title' => 'MyTestWidgetSide',
'Type' => $this->widgetToTest,
'Sort' => 0
)
),
'BottomBar' => array(
'new-1' => array(
'Title' => 'MyTestWidgetBottom',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$page = new WidgetAreaEditorTest_FakePage();
$editorSide->saveInto($page);
$editorBott->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
// Save again (after removing the SideBar's widget)
$_REQUEST = array(
'Widget' => array(
'SideBar' => array(
),
'BottomBar' => array(
$bottWidgets[0]->ID => array(
'Title' => 'MyTestWidgetBottom',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$editorSide->saveInto($page);
$editorBott->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
$this->assertEquals($page->BottomBar()->Widgets()->Count(), 1);
$this->assertEquals($bottWidgets[0]->Title(), 'MyTestWidgetBottom');
$this->assertEquals($page->SideBar()->Widgets()->Count(), 0);
$_REQUEST = $oldRequest;
}
function testDeletingAWidgetFromEachArea() {
$oldRequest = $_REQUEST;
// First get some widgets in there
$_REQUEST = array(
'Widget' => array(
'SideBar' => array(
'new-1' => array(
'Title' => 'MyTestWidgetSide',
'Type' => $this->widgetToTest,
'Sort' => 0
)
),
'BottomBar' => array(
'new-1' => array(
'Title' => 'MyTestWidgetBottom',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$page = new WidgetAreaEditorTest_FakePage();
$editorSide->saveInto($page);
$editorBott->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
// Save again (after removing the SideBar's widget)
$_REQUEST = array(
'Widget' => array(
'SideBar' => array(
),
'BottomBar' => array(
)
)
);
$editorSide->saveInto($page);
$editorBott->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
$this->assertEquals($page->BottomBar()->Widgets()->Count(), 0);
$this->assertEquals($page->SideBar()->Widgets()->Count(), 0);
$_REQUEST = $oldRequest;
}
function testEditingOneWidget() {
$oldRequest = $_REQUEST;
// First get some widgets in there
$_REQUEST = array(
'Widget' => array(
'SideBar' => array(
'new-1' => array(
'Title' => 'MyTestWidgetSide',
'Type' => $this->widgetToTest,
'Sort' => 0
)
),
'BottomBar' => array(
'new-1' => array(
'Title' => 'MyTestWidgetBottom',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$page = new WidgetAreaEditorTest_FakePage();
$editorSide->saveInto($page);
$editorBott->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
// Save again (after removing the SideBar's widget)
$_REQUEST = array(
'Widget' => array(
'SideBar' => array(
$sideWidgets[0]->ID => array(
'Title' => 'MyTestWidgetSide-edited',
'Type' => $this->widgetToTest,
'Sort' => 0
)
),
'BottomBar' => array(
$bottWidgets[0]->ID => array(
'Title' => 'MyTestWidgetBottom',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$editorSide->saveInto($page);
$editorBott->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
$this->assertEquals($page->BottomBar()->Widgets()->Count(), 1);
$this->assertEquals($page->SideBar()->Widgets()->Count(), 1);
$this->assertEquals($bottWidgets[0]->Title(), 'MyTestWidgetBottom');
$this->assertEquals($sideWidgets[0]->Title(), 'MyTestWidgetSide-edited');
$_REQUEST = $oldRequest;
}
function testEditingAWidgetFromEachArea() {
$oldRequest = $_REQUEST;
// First get some widgets in there
$_REQUEST = array(
'Widget' => array(
'SideBar' => array(
'new-1' => array(
'Title' => 'MyTestWidgetSide',
'Type' => $this->widgetToTest,
'Sort' => 0
)
),
'BottomBar' => array(
'new-1' => array(
'Title' => 'MyTestWidgetBottom',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$page = new WidgetAreaEditorTest_FakePage();
$editorSide->saveInto($page);
$editorBott->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
// Save again (after removing the SideBar's widget)
$_REQUEST = array(
'Widget' => array(
'SideBar' => array(
$sideWidgets[0]->ID => array(
'Title' => 'MyTestWidgetSide-edited',
'Type' => $this->widgetToTest,
'Sort' => 0
)
),
'BottomBar' => array(
$bottWidgets[0]->ID => array(
'Title' => 'MyTestWidgetBottom-edited',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$editorSide->saveInto($page);
$editorBott->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
$this->assertEquals($page->BottomBar()->Widgets()->Count(), 1);
$this->assertEquals($page->SideBar()->Widgets()->Count(), 1);
$this->assertEquals($bottWidgets[0]->Title(), 'MyTestWidgetBottom-edited');
$this->assertEquals($sideWidgets[0]->Title(), 'MyTestWidgetSide-edited');
$_REQUEST = $oldRequest;
}
function testEditAWidgetFromOneAreaAndDeleteAWidgetFromAnotherArea() {
$oldRequest = $_REQUEST;
// First get some widgets in there
$_REQUEST = array(
'Widget' => array(
'SideBar' => array(
'new-1' => array(
'Title' => 'MyTestWidgetSide',
'Type' => $this->widgetToTest,
'Sort' => 0
)
),
'BottomBar' => array(
'new-1' => array(
'Title' => 'MyTestWidgetBottom',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$page = new WidgetAreaEditorTest_FakePage();
$editorSide->saveInto($page);
$editorBott->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
// Save again (after removing the SideBar's widget)
$_REQUEST = array(
'Widget' => array(
'SideBar' => array(
$sideWidgets[0]->ID => array(
'Title' => 'MyTestWidgetSide-edited',
'Type' => $this->widgetToTest,
'Sort' => 0
)
),
'BottomBar' => array(
)
)
);
$editorSide->saveInto($page);
$editorBott->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
$this->assertEquals($page->BottomBar()->Widgets()->Count(), 0);
$this->assertEquals($page->SideBar()->Widgets()->Count(), 1);
$this->assertEquals($sideWidgets[0]->Title(), 'MyTestWidgetSide-edited');
$_REQUEST = $oldRequest;
}
}
class WidgetAreaEditorTest_FakePage extends Page implements TestOnly {
public static $has_one = array(
"SideBar" => "WidgetArea",
"BottomBar" => "WidgetArea",
);
}
class WidgetAreaEditorTest_TestWidget extends Widget implements TestOnly {
static $cmsTitle = "Test widget";
static $title = "Test widget";
static $description = "Test widget";
static $db = array(
'Title' => 'Varchar'
);
public function getCMSFields() {
$fields = new FieldList();
$fields->push(new TextField('Title'));
return $fields;
}
function Title() {
return $this->Title ? $this->Title : self::$title;
}
}

View File

@ -0,0 +1,88 @@
<?php
/**
* @package cms
* @subpackage tests
*/
class WidgetControllerTest extends FunctionalTest {
static $fixture_file = 'WidgetControllerTest.yml';
protected $extraDataObjects = array(
'WidgetControllerTestPage',
'WidgetControllerTest_Widget',
);
function testWidgetFormRendering() {
$page = $this->objFromFixture('WidgetControllerTestPage', 'page1');
$page->publish('Stage', 'Live');
$widget = $this->objFromFixture('WidgetControllerTest_Widget', 'widget1');
$response = $this->get($page->URLSegment);
$formAction = sprintf('%s/widget/%d/Form', $page->URLSegment, $widget->ID);
$this->assertContains(
$formAction,
$response->getBody(),
"Widget forms are rendered through WidgetArea templates"
);
}
function testWidgetFormSubmission() {
$page = $this->objFromFixture('WidgetControllerTestPage', 'page1');
$page->publish('Stage', 'Live');
$widget = $this->objFromFixture('WidgetControllerTest_Widget', 'widget1');
$this->get($page->URLSegment);
$response = $this->submitForm('Form_Form', null, array('TestValue'=>'Updated'));
$this->assertContains(
'TestValue: Updated',
$response->getBody(),
"Form values are submitted to correct widget form"
);
$this->assertContains(
sprintf('Widget ID: %d', $widget->ID),
$response->getBody(),
"Widget form acts on correct widget, as identified in the URL"
);
}
}
/**
* @package cms
* @subpackage tests
*/
class WidgetControllerTest_Widget extends Widget implements TestOnly {
static $db = array(
'TestValue' => 'Text'
);
}
/**
* @package cms
* @subpackage tests
*/
class WidgetControllerTest_Widget_Controller extends Widget_Controller implements TestOnly {
function Form() {
$widgetform = new Form(
$this,
'Form',
new FieldList(
new TextField('TestValue')
),
new FieldList(
new FormAction('doAction')
)
);
return $widgetform;
}
function doAction($data, $form) {
return sprintf('TestValue: %s\nWidget ID: %d',
$data['TestValue'],
$this->widget->ID
);
}
}

View File

@ -0,0 +1,10 @@
WidgetControllerTest_Widget:
widget1:
Title: Widget 1
WidgetArea:
area1:
Widgets: =>WidgetControllerTest_Widget.widget1
WidgetControllerTestPage:
page1:
Title: Page1
WidgetControllerTestSidebar: =>WidgetArea.area1

View File

@ -0,0 +1,27 @@
<?php
/**
* @package cms
* @subpackage tests
*/
class WidgetControllerTestPage extends Page implements TestOnly {
static $has_one = array(
'WidgetControllerTestSidebar' => 'WidgetArea'
);
}
/**
* @package cms
* @subpackage tests
*/
class WidgetControllerTestPage_Controller extends Page_Controller implements TestOnly {
/**
* Template selection doesnt work in test folders,
* so we enforce a template name.
*/
function getViewer($action) {
$templates = array('WidgetControllerTestPage');
return new SSViewer($templates);
}
}

View File

@ -0,0 +1 @@
$WidgetControllerTestSidebar

View File

@ -0,0 +1 @@
$Form