mirror of
https://github.com/silverstripe/silverstripe-widgets
synced 2024-10-22 17:05:54 +02:00
MINOR Initial commit
This commit is contained in:
commit
552a5006da
24
LICENSE
Normal file
24
LICENSE
Normal 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
329
README.md
Normal 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
0
_config.php
Normal file
142
code/form/WidgetAreaEditor.php
Normal file
142
code/form/WidgetAreaEditor.php
Normal 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
232
code/model/Widget.php
Normal 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
72
code/model/WidgetArea.php
Normal 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
3
templates/WidgetArea.ss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<% control WidgetControllers %>
|
||||||
|
$WidgetHolder
|
||||||
|
<% end_control %>
|
32
templates/WidgetAreaEditor.ss
Normal file
32
templates/WidgetAreaEditor.ss
Normal 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>
|
6
templates/WidgetDescription.ss
Normal file
6
templates/WidgetDescription.ss
Normal 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
12
templates/WidgetEditor.ss
Normal 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>
|
4
templates/WidgetHolder.ss
Normal file
4
templates/WidgetHolder.ss
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<div class="WidgetHolder $ClassName<% if FirstLast %> $FirstLast<% end_if %>">
|
||||||
|
<% if Title %><h3>$Title</h3><% end_if %>
|
||||||
|
$Content
|
||||||
|
</div>
|
471
tests/WidgetAreaEditorTest.php
Normal file
471
tests/WidgetAreaEditorTest.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
88
tests/WidgetControllerTest.php
Normal file
88
tests/WidgetControllerTest.php
Normal 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
10
tests/WidgetControllerTest.yml
Normal file
10
tests/WidgetControllerTest.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
WidgetControllerTest_Widget:
|
||||||
|
widget1:
|
||||||
|
Title: Widget 1
|
||||||
|
WidgetArea:
|
||||||
|
area1:
|
||||||
|
Widgets: =>WidgetControllerTest_Widget.widget1
|
||||||
|
WidgetControllerTestPage:
|
||||||
|
page1:
|
||||||
|
Title: Page1
|
||||||
|
WidgetControllerTestSidebar: =>WidgetArea.area1
|
27
tests/WidgetControllerTestPage.php
Normal file
27
tests/WidgetControllerTestPage.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
1
tests/WidgetControllerTestPage.ss
Normal file
1
tests/WidgetControllerTestPage.ss
Normal file
@ -0,0 +1 @@
|
|||||||
|
$WidgetControllerTestSidebar
|
1
tests/WidgetControllerTest_Widget.ss
Normal file
1
tests/WidgetControllerTest_Widget.ss
Normal file
@ -0,0 +1 @@
|
|||||||
|
$Form
|
Loading…
Reference in New Issue
Block a user