Merge pull request #143 from robbieaverill/feature/ss4-compat

Enhancement: SilverStripe 4 compatibility
This commit is contained in:
Damian Mooyman 2017-01-26 17:42:32 +13:00 committed by GitHub
commit 1fe4c3db70
38 changed files with 1032 additions and 730 deletions

View File

@ -6,4 +6,4 @@ checks:
duplication: true
filter:
paths: [code/*, tests/*]
paths: [src/*, tests/*]

View File

@ -1,31 +1,26 @@
# See https://github.com/silverstripe-labs/silverstripe-travis-support for setup details
# See https://github.com/silverstripe/silverstripe-travis-support for setup details
sudo: false
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
env:
- DB=MYSQL CORE_RELEASE=3.2
- DB=MYSQL CORE_RELEASE=4
matrix:
include:
- php: 5.6
env: DB=MYSQL CORE_RELEASE=3
- php: 5.6
env: DB=PGSQL CORE_RELEASE=3.2
allow_failures:
- php: 7.0
env: DB=PGSQL CORE_RELEASE=4
before_script:
- composer self-update || true
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support
- git clone git://github.com/silverstripe/silverstripe-travis-support.git ~/travis-support
- php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss
- cd ~/builds/ss
- composer install

16
.upgrade.yml Normal file
View File

@ -0,0 +1,16 @@
mappings:
WidgetContentControllerExtension: SilverStripe\Widgets\Controllers\WidgetContentControllerExtension
WidgetController: SilverStripe\Widgets\Controllers\WidgetController
Widget_Controller: SilverStripe\Widgets\Controllers\Widget_Controller
WidgetPageExtension: SilverStripe\Widgets\Extensions\WidgetPageExtension
WidgetAreaEditor: SilverStripe\Widgets\Forms\WidgetAreaEditor
Widget: SilverStripe\Widgets\Model\Widget
WidgetArea: SilverStripe\Widgets\Model\WidgetArea
WidgetAreaEditorTest: SilverStripe\Widgets\Tests\WidgetAreaEditorTest
WidgetAreaEditorTest_FakePage: SilverStripe\Widgets\Tests\WidgetAreaEditorTest\FakePage
WidgetAreaEditorTest_TestWidget: SilverStripe\Widgets\Tests\WidgetAreaEditorTest\TestWidget
WidgetControllerTest: SilverStripe\Widgets\Tests\WidgetControllerTest
WidgetControllerTest_Widget: SilverStripe\Widgets\Tests\WidgetControllerTest\TestWidget
WidgetControllerTest_WidgetController: SilverStripe\Widgets\Tests\WidgetControllerTest\TestWidgetController
WidgetControllerTestPage: SilverStripe\Widgets\Tests\WidgetControllerTest\TestPage
WidgetControllerTestPage_Controller: SilverStripe\Widgets\Tests\WidgetControllerTest\TestPageController

361
README.md
View File

@ -1,6 +1,9 @@
# Widgets Module
[![Build Status](https://secure.travis-ci.org/silverstripe/silverstripe-widgets.png?branch=1.1)](http://travis-ci.org/silverstripe/silverstripe-widgets)
[![Build Status](http://img.shields.io/travis/silverstripe/silverstripe-widgets.svg?style=flat-square)](https://travis-ci.org/silverstripe/silverstripe-widgets)
[![Code Quality](http://img.shields.io/scrutinizer/g/silverstripe/silverstripe-widgets.svg?style=flat-square)](https://scrutinizer-ci.com/g/silverstripe/silverstripe-widgets)
[![Version](http://img.shields.io/packagist/v/silverstripe/widgets.svg?style=flat-square)](https://packagist.org/packages/silverstripe/widgets)
[![License](http://img.shields.io/packagist/l/silverstripe/widgets.svg?style=flat-square)](LICENSE.md)
## Overview
@ -9,7 +12,7 @@ the sidebar of your website.
## Requirements
* SilverStripe 3.2
* SilverStripe 4.0
### Installation
@ -55,18 +58,28 @@ e.g.
**mysite/code/Page.php**
class Page extends SiteTree {
...
private static $has_one = array(
"MyWidgetArea" => "WidgetArea",
);
```php
<?php
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab("Root.Widgets", new WidgetAreaEditor("MyWidgetArea"));
return $fields;
}
}
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Widgets\Forms\WidgetAreaEditor;
use SilverStripe\Widgets\Model\WidgetArea;
class Page extends SiteTree
{
// ...
private static $has_one = array(
'MyWidgetArea' => WidgetArea::class
);
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Widgets', new WidgetAreaEditor('MyWidgetArea'));
return $fields;
}
}
```
In this case, you need to alter your templates to include the `$MyWidgetArea` placeholder.
@ -87,78 +100,98 @@ The class should extend the Widget class, and must specify three config variable
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
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
<?php
class FlickrWidget extends Widget {
private static $db = array(
"User" => "Varchar",
"Photoset" => "Varchar",
"Tags" => "Varchar",
"NumberToShow" => "Int"
);
```php
<?php
namespace Yourname\MyWidget;
private static $defaults = array(
"NumberToShow" => 8
);
use FlickrService;
use SilverStripe\Widgets\Model\Widget;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\NumericField;
use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;
use SilverStripe\View\Requirements;
private static $title = "Photos";
private static $cmsTitle = "Flickr Photos";
private static $description = "Shows flickr photos.";
class FlickrWidget extends Widget
{
private static $db = array(
'User' => 'Varchar',
'Photoset' => 'Varchar',
'Tags' => 'Varchar',
'NumberToShow' => 'Int'
);
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");
private static $defaults = array(
'NumberToShow' => 8
);
$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);
}
private static $title = 'Photos';
private static $cmsTitle = 'Flickr Photos';
private static $description = 'Shows flickr photos.';
$output = new ArrayList();
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 Photos()
{
// You'll need to install these yourself
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');
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")
);
}
}
$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 ArrayList();
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**
:::ss
<% control Photos %>
<a href="$Link" rel="lightbox" title="$Title"><img src="$Image" alt="$Title" /></a>
<% end_control %>
```
<% control Photos %>
<a href="$Link" rel="lightbox" title="$Title"><img src="$Image" alt="$Title" /></a>
<% end_control %>
```
## Releasing a widget
Follow the [standard procedures defined for releasing a SilverStripe module](http://doc.silverstripe.org/framework/en/3.1/topics/module-development).
Follow the [standard procedures defined for releasing a SilverStripe module](https://docs.silverstripe.org/en/4/developer_guides/extending/how_tos/publish_a_module).
Here is a composer template you can use.
@ -178,8 +211,8 @@ You need to finish off / change:
"type": "silverstripe-module",
"keywords" : ["widget"],
"require": {
"silverstripe/framework": "3.*",
"silverstripe/cms": "3.*"
"silverstripe/framework": "^4.0",
"silverstripe/cms": "^4.0"
},
"license": "BSD-2-Clause",
"authors": [
@ -190,6 +223,11 @@ You need to finish off / change:
],
"extra" : {
"installer-name": "widgets_"
},
"autoload": {
"psr-4": {
"Yourname\\MyWidget\\": "src/"
}
}
}
```
@ -203,55 +241,60 @@ define a merge variable in the Page Controller and include it in the Page Templa
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");
}
```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:
To render the widget, simply include `$SilverStripeFeed` in your template:
$SilverStripeFeed
```
$SilverStripeFeed
```
As directed in the definition of `SilverStripeFeed()`, the Widget will be rendered through the WidgetHolder template. This
is pre-defined at `widgets/templates/WidgetHolder.ss` and simply consists of:
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:
:::ss
<div class="WidgetHolder">
<h3>$Title</h3>
$Content
</div>
```
<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
To change the title of your widget, you need to override the `getTitle()` 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**
**widgets_yourWidget/src/YourWidgetWidget.php**
:::php
public function Title() {
return "Hello World!";
}
```php
public function getTitle()
{
return 'Hello World!';
}
```
but, you can do exactly the same by setting your `$title` variable.
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
A more common reason for overriding `getTitle()` 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.
:::php
public function Title() {
return $this->WidgetTitle ? $this->WidgetTitle : self::$title;
}
```php
public function getTitle()
{
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.
@ -263,69 +306,97 @@ sure that your controller follows the usual naming conventions, and it will be a
**mysite/code/MyWidget.php**
:::php
class MyWidget extends Widget {
private static $db = array(
'TestValue' => 'Text'
);
}
```php
<?php
class MyWidget_Controller extends WidgetController {
public function MyFormName() {
return new Form(
$this,
'MyFormName',
new FieldList(
new TextField('TestValue')
),
new FieldList(
new FormAction('doAction')
)
);
}
namespace Yourname\MyWidget;
public function doAction($data, $form) {
// $this->widget points to the widget
}
}
use SilverStripe\Widgets\Model\Widget;
class MyWidget extends Widget
{
private static $db = array(
'TestValue' => 'Text'
);
}
```
```php
<?php
namespace Yourname\MyWidget;
use SilverStripe\Widgets\Controllers\WidgetController;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
class MyWidgetController extends WidgetController
{
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**
**mysite/templates/Yourname/MyWidget/MyWidget.ss**
:::ss
$Content
$MyFormName
```
$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*.
**Note:** The necessary controller actions are only present in subclasses of `PageController`. 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??
**Note:** This applies to old versions of the blog module. The latest version of this module does not contain `BlogHolder.php`.
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
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
<?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.Widgets", new WidgetAreaEditor("Sidebar")); COMMENT OUT
........
```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.Widgets", new WidgetAreaEditor("Sidebar")); COMMENT OUT
}
// ...
}
```
Then you can use the Widget area you defined on Page.php
@ -339,4 +410,4 @@ and any new translations will be merged back to the project source code.
Please use [https://www.transifex.com/projects/p/silverstripe-widgets/](https://www.transifex.com/projects/p/silverstripe-widgets/) to contribute translations,
rather than sending pull requests with YAML files.
See the ["i18n" topic](https://docs.silverstripe.org/en/3.2/developer_guides/i18n/) on doc.silverstripe.org for more details.
See the ["i18n" topic](https://docs.silverstripe.org/en/4/developer_guides/i18n/) on doc.silverstripe.org for more details.

View File

@ -0,0 +1 @@
<?php

View File

@ -1,8 +1,8 @@
---
Name: contentcontrollerurlhandler
---
ContentController:
SilverStripe\CMS\Controllers\ContentController:
extensions:
- WidgetContentControllerExtension
- SilverStripe\Widgets\Controllers\WidgetContentControllerExtension
url_handlers:
'widget/$ID!': 'handleWidget'
'widget/$ID!': 'handleWidget'

7
_config/legacy.yml Normal file
View File

@ -0,0 +1,7 @@
---
Name: widgetslegacymapping
---
SilverStripe\ORM\DatabaseAdmin:
classname_value_remapping:
Widget: SilverStripe\Widgets\Model\Widget
WidgetArea: SilverStripe\Widgets\Model\WidgetArea

View File

@ -1,3 +1,3 @@
Director:
SilverStripe\Control\Director:
rules:
'WidgetController//$Action/$ID/$OtherID': 'WidgetController'
'WidgetController//$Action/$ID/$OtherID': 'SilverStripe\Widgets\Controllers\WidgetController'

View File

@ -1,71 +0,0 @@
<?php
/**
* Add this to ContentController to enable widgets
*
* @package widgets
*/
class WidgetContentControllerExtension extends Extension
{
/**
*
* @var array
*/
private static $allowed_actions = array(
'handleWidget'
);
/**
* Handles widgets attached to a page through one or more {@link WidgetArea}
* elements.
*
* Iterated through each $has_one relation with a {@link WidgetArea} and
* looks for connected widgets by their database identifier.
*
* Assumes URLs in the following format: <URLSegment>/widget/<Widget-ID>.
*
* @return RequestHandler
*/
public function handleWidget()
{$SQL_id = $this->owner->getRequest()->param('ID');
if(!$SQL_id) {return false;
}
/** @var SiteTree $widgetOwner */
$widgetOwner = $this->owner->data();
while($widgetOwner->InheritSideBar && $widgetOwner->Parent()->exists()){
$widgetOwner = $widgetOwner->Parent();
}
// find WidgetArea relations
$widgetAreaRelations = array();
$hasOnes = $widgetOwner->hasOne();
if(!$hasOnes) {
return false;
}
foreach ($hasOnes as $hasOneName => $hasOneClass) {
if ($hasOneClass == 'WidgetArea' || is_subclass_of($hasOneClass, 'WidgetArea')) {
$widgetAreaRelations[] = $hasOneName;
}
}
// find widget
$widget = null;
foreach ($widgetAreaRelations as $widgetAreaRelation) {
if ($widget) {
break;
}
$widget = $widgetOwner->$widgetAreaRelation()->Widgets()
->filter('ID', $SQL_id)
->First();
}
if (!$widget) {
user_error('No widget found', E_USER_ERROR);
}
return $widget->getController();
}
}

View File

@ -1,145 +0,0 @@
<?php
/**
* 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 widgets
*/
class WidgetController extends Controller
{
/**
* @var Widget
*/
protected $widget;
/**
* @var array
*/
private static $allowed_actions = array(
'editablesegment'
);
/**
* @param Widget $widget
*/
public function __construct($widget = null)
{
if ($widget) {
$this->widget = $widget;
$this->failover = $widget;
}
parent::__construct();
}
/**
* @param string $action
* @return string
*/
public function Link($action = null)
{
$id = ($this->widget) ? $this->widget->ID : null;
$segment = Controller::join_links('widget', $id, $action);
$page = Director::get_current_page();
if($page && !($page instanceof WidgetController)) {return $page->Link($segment);
}
if ($controller = $this->getParentController()) {return $controller->Link($segment);
}
return $segment;
}
/**
* Cycles up the controller stack until it finds a non-widget controller
* This is needed becauseController::currreturns the widget controller,
* which means anyLinkfunction turns into endless loop.
*
* @return Controller
*/
public function getParentController()
{
foreach(Controller::$controller_stack as $controller) {
if (!($controller instanceof WidgetController)) {
return $controller;
}
}
return false;
}
/**
* @return Widget
*/
public function getWidget()
{return $this->widget;
}
/**
* Overloaded from {@link Widget->Content()} to allow for controller / form
* linking.
*
* @return string HTML
*/
public 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
*/
public 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
*/
public function editablesegment() {
// use left and main to set the html config
$leftandmain = LeftAndMain::create();
$leftandmain->init();
$className = $this->urlParams['ID'];
if (class_exists('Translatable') && Member::currentUserID()) {
// set current locale based on logged in user's locale
$locale = Member::currentUser()->Locale;
i18n::set_locale($locale);
}
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";
}
}
}
/**
* @deprecated Use WidgetController
* @package widgets
*/
class Widget_Controller extends WidgetController
{
}

View File

@ -1,22 +1,33 @@
{
"name": "silverstripe/widgets",
"description": "Widgets are small pieces of functionality such as showing the latest Comments or Flickr Photos. They normally display on the sidebar of your website.",
"type": "silverstripe-module",
"keywords": ["silverstripe", "widgets", "blog"],
"authors": [
{
"name": "Ingo Schommer",
"email": "ingo@silverstripe.com"
}
],
"require": {
"silverstripe/framework": "^4",
"silverstripe/cms": "^4"
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"license": "BSD-3-Clause"
"name": "silverstripe/widgets",
"description": "Widgets are small pieces of functionality such as showing the latest Comments or Flickr Photos. They normally display on the sidebar of your website.",
"type": "silverstripe-module",
"keywords": ["silverstripe", "widgets", "blog"],
"authors": [
{
"name": "Ingo Schommer",
"email": "ingo@silverstripe.com"
}
],
"require": {
"silverstripe/framework": "^4.0@dev",
"silverstripe/cms": "^4.0@dev"
},
"require-dev": {
"phpunit/PHPUnit": "~4.8"
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"SilverStripe\\Widgets\\": "src/",
"SilverStripe\\Widgets\\Tests\\": "tests/"
}
},
"license": "BSD-3-Clause",
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@ -4,7 +4,7 @@ Contributions are welcome! Create an issue, explaining a bug or proposal. Submit
## Releasing a widget
Follow the [standard procedures defined for releasing a SilverStripe module](http://doc.silverstripe.org/framework/en/3.1/topics/module-development).
Follow the [standard procedures defined for releasing a SilverStripe module](https://docs.silverstripe.org/en/4/developer_guides/extending/how_tos/publish_a_module).
Here is a composer template you can use.
@ -36,6 +36,11 @@ You need to finish off / change:
],
"extra" : {
"installer-name": "widgets_"
},
"autoload": {
"psr-4": {
"Yourname\\MyWidget\\": "src/"
}
}
}
```
```

View File

@ -12,7 +12,9 @@ You'll also need to run `dev/build`.
Install the module through [composer](http://getcomposer.org):
composer require silverstripe/widgets
```sh
$ composer require silverstripe/widgets
```
Widgets are essentially database relations to other models, mostly page types.
By default, they're not added to any of your own models. The easiest and most common
@ -20,10 +22,11 @@ way to get started would be to create a single collection of widgets under the
name "SideBar" on your `Page` class. This is handled by an extension which you
can enable through your `config.yml`:
:::yml
Page:
extensions:
- WidgetPageExtension
```yaml
Page:
extensions:
- SilverStripe\Widgets\Extensions\WidgetPageExtension
```
Run a `dev/build`, and adjust your templates to include the resulting sidebar view.
The placeholder is called `$SideBarView`, and loops through all widgets assigned
@ -31,7 +34,7 @@ to the current page.
Alternatively, you can add one or more widget collections to your own page types.
Here's an example on how to just add widgets to a `MyPage` type, and call it
`MyWidgetArea` instead.
`MyWidgetArea` instead. Please ensure you add the correct namespaces for your module.
### Installing a widget
@ -56,18 +59,28 @@ e.g.
**mysite/code/Page.php**
class Page extends SiteTree {
...
private static $has_one = array(
"MyWidgetArea" => "WidgetArea",
);
```php
<?php
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab("Root.Widgets", new WidgetAreaEditor("MyWidgetArea"));
return $fields;
}
}
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Widgets\Forms\WidgetAreaEditor;
use SilverStripe\Widgets\Model\WidgetArea;
class Page extends SiteTree
{
// ...
private static $has_one = array(
'MyWidgetArea' => WidgetArea::class
);
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Widgets', new WidgetAreaEditor('MyWidgetArea'));
return $fields;
}
}
```
In this case, you need to alter your templates to include the `$MyWidgetArea` placeholder.
@ -88,73 +101,93 @@ The class should extend the Widget class, and must specify three config variable
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
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
<?php
class FlickrWidget extends Widget {
private static $db = array(
"User" => "Varchar",
"Photoset" => "Varchar",
"Tags" => "Varchar",
"NumberToShow" => "Int"
);
```php
<?php
namespace Yourname\MyWidget;
private static $defaults = array(
"NumberToShow" => 8
);
use FlickrService;
use SilverStripe\Widgets\Model\Widget;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\NumericField;
use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;
use SilverStripe\View\Requirements;
private static $title = "Photos";
private static $cmsTitle = "Flickr Photos";
private static $description = "Shows flickr photos.";
class FlickrWidget extends Widget
{
private static $db = array(
'User' => 'Varchar',
'Photoset' => 'Varchar',
'Tags' => 'Varchar',
'NumberToShow' => 'Int'
);
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");
private static $defaults = array(
'NumberToShow' => 8
);
$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);
}
private static $title = 'Photos';
private static $cmsTitle = 'Flickr Photos';
private static $description = 'Shows flickr photos.';
$output = new ArrayList();
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 Photos()
{
// You'll need to install these yourself
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');
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")
);
}
}
$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 ArrayList();
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**
:::ss
<% control Photos %>
<a href="$Link" rel="lightbox" title="$Title"><img src="$Image" alt="$Title" /></a>
<% end_control %>
```
<% control Photos %>
<a href="$Link" rel="lightbox" title="$Title"><img src="$Image" alt="$Title" /></a>
<% end_control %>
```
## Limiting Allowed Widgets for a Pagetype
@ -162,8 +195,15 @@ You can lock down a particular `WidgetAreaEditor` to only allow adding certain w
**GreatPage.php**
:::php
$fields->addFieldToTab(
'Root.Widgets',
new WidgetAreaEditor('PhenomenalWidgetArea', ['ParticularlyEpicWidget', 'LessGreatWidget'])
);
```php
$fields->addFieldToTab(
'Root.Widgets',
new WidgetAreaEditor(
'PhenomenalWidgetArea',
[
'Fully\\Qualified\\ParticularlyEpicWidget',
'Yourname\\MyModule\\LessGreatWidget'
]
)
);
```

View File

@ -5,8 +5,8 @@ the sidebar of your website. To check out a what a [Widget](http://silverstripe.
[demo site](http://demo.silverstripe.org/)
[![Build Status](http://img.shields.io/travis/silverstripe/silverstripe-widgets.svg?style=flat-square)](https://travis-ci.org/silverstripe/silverstripe-widgets)
[![Code Quality](http://img.shields.io/scrutinizer/g/silverstripe/silverstripe-widgets.svg?style=flat-square)](https://scrutinizer-ci.com/g/silverstripe/silverstripe-widgets)
[![Version](http://img.shields.io/packagist/v/silverstripe/silverstripe-widgets.svg?style=flat-square)](https://packagist.org/packages/silverstripe/silverstripe-widgets)
[![License](http://img.shields.io/packagist/l/silverstripe/silverstripe-widgets.svg?style=flat-square)](LICENSE.md)
[![Version](http://img.shields.io/packagist/v/silverstripe/widgets.svg?style=flat-square)](https://packagist.org/packages/silverstripe/widgets)
[![License](http://img.shields.io/packagist/l/silverstripe/widgets.svg?style=flat-square)](LICENSE.md)
## Share Links
@ -21,4 +21,4 @@ The generated share links have a public key and hash. There can be any number of
This module was created by [SilverStripe](https://twitter.com/silverstripe). You can ask questions on Twitter.
You can report bugs or request features on [GitHub](https://github.com/silverstripe/silverstripe-widgets/issues).
You can report bugs or request features on [GitHub](https://github.com/silverstripe/silverstripe-widgets/issues).

View File

@ -0,0 +1,79 @@
<?php
namespace SilverStripe\Widgets\Controllers;
use SilverStripe\Core\Extension;
use SilverStripe\Widgets\Model\WidgetArea;
/**
* Add this to ContentController to enable widgets
*
* @package widgets
*/
class WidgetContentControllerExtension extends Extension
{
/**
*
* @var array
*/
private static $allowed_actions = array(
'handleWidget'
);
/**
* Handles widgets attached to a page through one or more {@link WidgetArea}
* elements.
*
* Iterated through each $has_one relation with a {@link WidgetArea} and
* looks for connected widgets by their database identifier.
*
* Assumes URLs in the following format: <URLSegment>/widget/<Widget-ID>.
*
* @return RequestHandler
*/
public function handleWidget()
{
$SQL_id = $this->owner->getRequest()->param('ID');
if (!$SQL_id) {
return false;
}
/** @var SiteTree $widgetOwner */
$widgetOwner = $this->owner->data();
while ($widgetOwner->InheritSideBar && $widgetOwner->Parent()->exists()) {
$widgetOwner = $widgetOwner->Parent();
}
// find WidgetArea relations
$widgetAreaRelations = array();
$hasOnes = $widgetOwner->hasOne();
if (!$hasOnes) {
return false;
}
foreach ($hasOnes as $hasOneName => $hasOneClass) {
if ($hasOneClass == WidgetArea::class || is_subclass_of($hasOneClass, WidgetArea::class)) {
$widgetAreaRelations[] = $hasOneName;
}
}
// find widget
$widget = null;
foreach ($widgetAreaRelations as $widgetAreaRelation) {
if ($widget) {
break;
}
$widget = $widgetOwner->$widgetAreaRelation()->Widgets()
->filter('ID', $SQL_id)
->First();
}
if (!$widget) {
user_error('No widget found', E_USER_ERROR);
}
return $widget->getController();
}
}

View File

@ -0,0 +1,155 @@
<?php
namespace SilverStripe\Widgets\Controllers;
use SilverStripe\Admin\LeftAndMain;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Core\ClassInfo;
use SilverStripe\i18n\i18n;
use SilverStripe\Security\Member;
use SilverStripe\Widgets\Model\Widget;
/**
* 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 widgets
*/
class WidgetController extends Controller
{
/**
* @var Widget
*/
protected $widget;
/**
* @var array
*/
private static $allowed_actions = array(
'editablesegment'
);
/**
* @param Widget $widget
*/
public function __construct($widget = null)
{
if ($widget) {
$this->widget = $widget;
$this->failover = $widget;
}
parent::__construct();
}
/**
* @param string $action
* @return string
*/
public function Link($action = null)
{
$id = ($this->widget) ? $this->widget->ID : null;
$segment = Controller::join_links('widget', $id, $action);
$page = Director::get_current_page();
if ($page && !($page instanceof WidgetController)) {
return $page->Link($segment);
}
if ($controller = $this->getParentController()) {
return $controller->Link($segment);
}
return $segment;
}
/**
* Cycles up the controller stack until it finds a non-widget controller
* This is needed becauseController::currreturns the widget controller,
* which means anyLinkfunction turns into endless loop.
*
* @return Controller
*/
public function getParentController()
{
foreach (Controller::$controller_stack as $controller) {
if (!($controller instanceof WidgetController)) {
return $controller;
}
}
return false;
}
/**
* @return Widget
*/
public function getWidget()
{
return $this->widget;
}
/**
* Overloaded from {@link Widget->Content()} to allow for controller / form
* linking.
*
* @return string HTML
*/
public 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
*/
public 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
*/
public function editablesegment()
{
// use left and main to set the html config
$leftandmain = LeftAndMain::create();
$leftandmain->doInit();
// Decode if fully qualified - @see Widget::ClassName
$className = str_replace('_', '\\', $this->urlParams['ID']);
if (class_exists('Translatable') && Member::currentUserID()) {
// set current locale based on logged in user's locale
$locale = Member::currentUser()->Locale;
i18n::set_locale($locale);
}
if (class_exists($className) && is_subclass_of($className, Widget::class)) {
$obj = new $className();
return $obj->EditableSegment();
} else {
user_error("Bad widget class: $className", E_USER_WARNING);
return "Bad widget class name given";
}
}
}

View File

@ -1,4 +1,13 @@
<?php
namespace SilverStripe\Widgets\Extensions;
use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\FieldList;
use SilverStripe\ORM\DataExtension;
use SilverStripe\Widgets\Forms\WidgetAreaEditor;
use SilverStripe\Widgets\Model\WidgetArea;
/**
* Adds a single {@link WidgetArea} called "SideBar" to {@link Page} classes.
* Adjust your templates to render the resulting
@ -19,7 +28,7 @@ class WidgetPageExtension extends DataExtension
);
private static $has_one = array(
'SideBar' => 'WidgetArea'
'SideBar' => WidgetArea::class
);
public function updateCMSFields(FieldList $fields)
@ -39,8 +48,7 @@ class WidgetPageExtension extends DataExtension
*/
public function SideBarView()
{
if (
$this->owner->InheritSideBar
if ($this->owner->InheritSideBar
&& ($parent = $this->owner->getParent())
&& $parent->hasMethod('SideBarView')
) {
@ -49,7 +57,7 @@ class WidgetPageExtension extends DataExtension
return $this->owner->SideBar();
}
}
public function onBeforeDuplicate($duplicatePage)
{
if ($this->owner->hasField('SideBarID')) {

View File

@ -1,5 +1,18 @@
<?php
namespace SilverStripe\Widgets\Forms;
use Exception;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\FormField;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\Widgets\Forms\WidgetAreaEditor;
use SilverStripe\Widgets\Model\Widget;
use SilverStripe\View\Requirements;
/**
* Special field type for selecting and configuring widgets on a page.
*
@ -12,11 +25,11 @@ class WidgetAreaEditor extends FormField
* @param array $widgetClasses
* @param int $maxWidgets
*/
public function __construct($name, $widgetClasses = array('Widget'), $maxWidgets = 0)
public function __construct($name, $widgetClasses = array(Widget::class), $maxWidgets = 0)
{
$this->MaxWidgets = $maxWidgets;
$this->widgetClasses = $widgetClasses;
parent::__construct($name);
}
@ -30,7 +43,7 @@ class WidgetAreaEditor extends FormField
Requirements::css('widgets/css/WidgetAreaEditor.css');
Requirements::javascript('widgets/javascript/WidgetAreaEditor.js');
return $this->renderWith("WidgetAreaEditor");
return $this->renderWith(WidgetAreaEditor::class);
}
/**
@ -44,15 +57,15 @@ class WidgetAreaEditor extends FormField
foreach ($this->widgetClasses as $widgetClass) {
$classes = ClassInfo::subclassesFor($widgetClass);
if (isset($classes['Widget'])) {
unset($classes['Widget']);
} elseif (isset($classes[0]) && $classes[0] == 'Widget') {
if (isset($classes[Widget::class])) {
unset($classes[Widget::class]);
} elseif (isset($classes[0]) && $classes[0] == Widget::class) {
unset($classes[0]);
}
foreach ($classes as $class) {
$available = Config::inst()->get($class, 'only_available_in');
if (!empty($available) && is_array($available)) {
if (in_array($this->Name, $available)) {
$widgets->push(singleton($class));
@ -62,7 +75,7 @@ class WidgetAreaEditor extends FormField
}
}
}
return $widgets;
}
@ -72,8 +85,8 @@ class WidgetAreaEditor extends FormField
public function UsedWidgets()
{
// Call class_exists() to load Widget.php earlier and avoid a segfault
class_exists('Widget');
class_exists(Widget::class);
$relationName = $this->name;
$widgets = $this->form->getRecord()->getComponent($relationName)->Items();
@ -101,6 +114,7 @@ class WidgetAreaEditor extends FormField
/**
* @param DataObjectInterface $record
* @throws Exception if no form could be retrieved
*/
public function saveInto(DataObjectInterface $record)
{
@ -109,15 +123,15 @@ class WidgetAreaEditor extends FormField
$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;
@ -133,7 +147,6 @@ class WidgetAreaEditor extends FormField
$widgetAreaData = $widgetData[$this->getName()];
foreach ($widgetAreaData as $newWidgetID => $newWidgetData) {
// Sometimes the id is "new-1" or similar, ensure this doesn't get into the query
if (!is_numeric($newWidgetID)) {
$newWidgetID = 0;
@ -156,7 +169,7 @@ class WidgetAreaEditor extends FormField
if (!$widget
&& !empty($newWidgetData['Type'])
&& class_exists($newWidgetData['Type'])
&& is_subclass_of($newWidgetData['Type'], 'Widget')
&& is_subclass_of($newWidgetData['Type'], Widget::class)
) {
$widget = Injector::inst()->create($newWidgetData['Type']);
$widget->ID = 0;
@ -167,12 +180,11 @@ class WidgetAreaEditor extends FormField
if ($widget->ParentID == 0) {
$widget->ParentID = $record->$name()->ID;
}
$widget->populateFromPostData($newWidgetData);
}
}
}
// remove the fields not saved
if ($missingWidgets) {
foreach ($missingWidgets as $removedWidget) {

View File

@ -1,12 +1,24 @@
<?php
namespace SilverStripe\Widgets\Model;
use Exception;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\HiddenField;
use SilverStripe\Forms\TextField;
use SilverStripe\ORM\DataObject;
use SilverStripe\Widgets\Model\WidgetArea;
/**
* Widgets let CMS authors drag and drop small pieces of functionality into
* defined areas of their websites.
*
* You can use forms in widgets by implementing a {@link WidgetController}.
*
* See {@link Widget_Controller} for more information.
* See {@link WidgetController} for more information.
*
* @package widgets
*/
@ -42,7 +54,7 @@ class Widget extends DataObject
* @var array
*/
private static $has_one = array(
"Parent" => "WidgetArea",
"Parent" => WidgetArea::class,
);
/**
@ -72,6 +84,11 @@ class Widget extends DataObject
'CMSTitle' => 'Title'
);
/**
* @var string
*/
private static $table_name = 'Widget';
/**
* @var WidgetController
*/
@ -122,15 +139,6 @@ class Widget extends DataObject
return $this->renderWith(array_reverse(ClassInfo::ancestry($this->class)));
}
/**
* @return string
* @deprecated
*/
public function Title()
{
return $this->getTitle();
}
/**
* Get the frontend title for this widget
*
@ -142,15 +150,6 @@ class Widget extends DataObject
?: _t($this->class . '.TITLE', $this->config()->title);
}
/**
* @return string
* @deprecated
*/
public function CMSTitle()
{
return $this->getCMSTitle();
}
/**
* @return string
*/
@ -159,15 +158,6 @@ class Widget extends DataObject
return _t($this->class . '.CMSTITLE', $this->config()->cmsTitle);
}
/**
* @return string
* @deprecated
*/
public function Description()
{
return $this->getDescription();
}
/**
* @return string
*/
@ -217,8 +207,13 @@ class Widget extends DataObject
$outputFields = new FieldList();
$this->FormID = $this->ID ?: uniqid();
$outputFields->push(HiddenField::create('Widget[' . $this->FormID . '][FormID]', 'FormID',
$this->FormID)->addExtraClass('formid'));
$outputFields->push(
HiddenField::create(
'Widget[' . $this->FormID . '][FormID]',
'FormID',
$this->FormID
)->addExtraClass('formid')
);
foreach ($fields as $field) {
$name = $field->getName();
@ -236,11 +231,14 @@ class Widget extends DataObject
}
/**
* A fully qualified class name is returned with underscores instead of backslashes so it is HTML safe. Dashes
* can't be used as they're handled in the Javascript for other purposes.
*
* @return string
*/
public function ClassName()
{
return $this->class;
return str_replace('\\', '_', $this->class);
}
/**
@ -263,14 +261,7 @@ class Widget extends DataObject
}
foreach (array_reverse(ClassInfo::ancestry($this->class)) as $widgetClass) {
$controllerClass = "{$widgetClass}_Controller";
if (class_exists($controllerClass)) {
break;
}
$controllerClass = "{$widgetClass}Controller";
if (class_exists($controllerClass)) {
break;
}

View File

@ -1,5 +1,11 @@
<?php
namespace SilverStripe\Widgets\Model;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
use SilverStripe\Widgets\Model\Widget;
/**
* Represents a set of widgets shown on a page.
*
@ -11,31 +17,36 @@ class WidgetArea extends DataObject
* @var array
*/
private static $has_many = array(
"Widgets" => "Widget"
"Widgets" => Widget::class
);
/**
* @var string
*/
private static $table_name = 'WidgetArea';
/**
*
* @var string
*/
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
* 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 WidgetController}.
*
*
* @return SS_List - Collection of {@link WidgetController} instances.
*/
public function WidgetControllers()
{
$controllers = new ArrayList();
$items = $this->ItemsToRender();
if (!is_null($items)){
if (!is_null($items)) {
foreach ($items as $widget) {
$controller = $widget->getController();
$controller->init();
$controller->doInit();
$controllers->push($controller);
}
}

View File

@ -4,8 +4,8 @@
<h2><% _t('WidgetAreaEditor_ss.AVAILABLE', 'Available Widgets') %></h2>
<p class="message"><% _t('WidgetAreaEditor_ss.AVAILWIDGETS', 'Click a widget title below to use it on this page.') %></p>
<div class="availableWidgets" id="availableWidgets-$Name">
<% if AvailableWidgets %>
<% loop AvailableWidgets %>
<% if $AvailableWidgets %>
<% loop $AvailableWidgets %>
$DescriptionSegment
<% end_loop %>
<% else %>
@ -18,10 +18,10 @@
<div class="usedWidgetsHolder">
<h2><% _t('WidgetAreaEditor_ss.INUSE', 'Widgets currently used') %></h2>
<p class="message"><% _t('WidgetAreaEditor_ss.TOSORT', 'To sort currently used widgets on this page, drag them up and down.') %></p>
<div class="usedWidgets" id="usedWidgets-$Name">
<% if UsedWidgets %>
<% loop UsedWidgets %>
<% if $UsedWidgets %>
<% loop $UsedWidgets %>
$EditableSegment
<% end_loop %>
<% else %>
@ -29,4 +29,4 @@
<% end_if %>
</div>
</div>
</div>
</div>

View File

@ -3,4 +3,4 @@
<div class="widgetDescription">
<p>$Description</p>
</div>
</div>
</div>

View File

@ -10,5 +10,5 @@
<% end_if %>
<input type="hidden" name="$Name[Type]" value="$ClassName" />
<input type="hidden" name="$Name[Sort]" value="$Sort" />
<p class="deleteWidget"><span class="widgetDelete ss-ui-button"><% _t('WidgetEditor_ss.DELETE', 'Delete') %></span></p>
</div>
<p class="deleteWidget"><span class="widgetDelete btn btn-danger"><% _t('WidgetEditor_ss.DELETE', 'Delete') %></span></p>
</div>

View File

@ -1,4 +1,21 @@
<?php
namespace SilverStripe\Widgets\Tests;
use Page;
use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Widgets\Extensions\WidgetPageExtension;
use SilverStripe\Widgets\Forms\WidgetAreaEditor;
use SilverStripe\Widgets\Model\Widget;
use SilverStripe\Widgets\Tests\WidgetAreaEditorTest\FakePage;
use SilverStripe\Widgets\Tests\WidgetAreaEditorTest\TestWidget;
/**
* @package cms
* @subpackage tests
@ -8,21 +25,19 @@ class WidgetAreaEditorTest extends SapphireTest
/**
* This is the widget you want to use for your unit tests.
*/
protected $widgetToTest = 'WidgetAreaEditorTest_TestWidget';
protected $widgetToTest = TestWidget::class;
protected $extraDataObjects = array(
'WidgetAreaEditorTest_FakePage',
'WidgetAreaEditorTest_TestWidget',
FakePage::class,
TestWidget::class,
);
protected $usesDatabase = true;
protected $requiredExtensions = array(
"SiteTree" => array(
"WidgetPageExtension"
)
SiteTree::class => array(WidgetPageExtension::class)
);
public function testFillingOneArea()
{
$data = array(
@ -36,14 +51,19 @@ class WidgetAreaEditorTest extends SapphireTest
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$request = new HTTPRequest('get', 'post', array(), $data);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(new ContentController(), 'Form', new FieldList($editorSide, $editorBott), new FieldList());
$form = new Form(
new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$form->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
$page = new FakePage();
$form->saveInto($page);
$page->write();
@ -75,30 +95,35 @@ class WidgetAreaEditorTest extends SapphireTest
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$request = new HTTPRequest('get', 'post', array(), $data);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(new ContentController(), 'Form', new FieldList($editorSide, $editorBott), new FieldList());
$form = new Form(
new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$form->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
$page = new FakePage();
$form->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');
$this->assertEquals($sideWidgets[0]->getTitle(), 'MyTestWidgetSide');
$this->assertEquals($bottWidgets[0]->getTitle(), 'MyTestWidgetBottom');
}
public function testDeletingOneWidgetFromOneArea()
{
// First get some widgets in there
@ -120,13 +145,18 @@ class WidgetAreaEditorTest extends SapphireTest
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$request = new HTTPRequest('get', 'post', array(), $data);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(new ContentController(), 'Form', new FieldList($editorSide, $editorBott), new FieldList());
$form = new Form(
new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$form->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
$page = new FakePage();
$form->saveInto($page);
$page->write();
@ -135,7 +165,7 @@ class WidgetAreaEditorTest extends SapphireTest
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
// Save again (after removing the SideBar's widget)
$data = array(
'Widget' => array(
@ -150,7 +180,7 @@ class WidgetAreaEditorTest extends SapphireTest
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$request = new HTTPRequest('get', 'post', array(), $data);
$form->setRequest($request);
$form->saveInto($page);
@ -160,9 +190,9 @@ class WidgetAreaEditorTest extends SapphireTest
$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($bottWidgets[0]->getTitle(), 'MyTestWidgetBottom');
$this->assertEquals($page->SideBar()->Widgets()->Count(), 0);
}
@ -187,13 +217,18 @@ class WidgetAreaEditorTest extends SapphireTest
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$request = new HTTPRequest('get', 'post', array(), $data);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(new ContentController(), 'Form', new FieldList($editorSide, $editorBott), new FieldList());
$form = new Form(
new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$form->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
$page = new FakePage();
$form->saveInto($page);
$page->write();
@ -202,7 +237,7 @@ class WidgetAreaEditorTest extends SapphireTest
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
// Save again (after removing the SideBar's widget)
$data = array(
'Widget' => array(
@ -212,21 +247,21 @@ class WidgetAreaEditorTest extends SapphireTest
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$request = new HTTPRequest('get', 'post', array(), $data);
$form->setRequest($request);
$form->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);
}
public function testEditingOneWidget()
{
// First get some widgets in there
@ -248,13 +283,18 @@ class WidgetAreaEditorTest extends SapphireTest
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$request = new HTTPRequest('get', 'post', array(), $data);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(new ContentController(), 'Form', new FieldList($editorSide, $editorBott), new FieldList());
$form = new Form(
new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$form->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
$page = new FakePage();
$form->saveInto($page);
$page->write();
@ -263,7 +303,7 @@ class WidgetAreaEditorTest extends SapphireTest
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
// Save again (after removing the SideBar's widget)
$data = array(
'Widget' => array(
@ -283,7 +323,7 @@ class WidgetAreaEditorTest extends SapphireTest
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$request = new HTTPRequest('get', 'post', array(), $data);
$form->setRequest($request);
$form->saveInto($page);
@ -293,11 +333,11 @@ class WidgetAreaEditorTest extends SapphireTest
$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');
$this->assertEquals($bottWidgets[0]->getTitle(), 'MyTestWidgetBottom');
$this->assertEquals($sideWidgets[0]->getTitle(), 'MyTestWidgetSide-edited');
}
public function testEditingAWidgetFromEachArea()
@ -321,13 +361,18 @@ class WidgetAreaEditorTest extends SapphireTest
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$request = new HTTPRequest('get', 'post', array(), $data);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(new ContentController(), 'Form', new FieldList($editorSide, $editorBott), new FieldList());
$form = new Form(
new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$form->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
$page = new FakePage();
$form->saveInto($page);
$page->write();
@ -336,7 +381,7 @@ class WidgetAreaEditorTest extends SapphireTest
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
// Save again (after removing the SideBar's widget)
$data = array(
'Widget' => array(
@ -356,7 +401,7 @@ class WidgetAreaEditorTest extends SapphireTest
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$request = new HTTPRequest('get', 'post', array(), $data);
$form->setRequest($request);
$form->saveInto($page);
@ -366,13 +411,13 @@ class WidgetAreaEditorTest extends SapphireTest
$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');
$this->assertEquals($bottWidgets[0]->getTitle(), 'MyTestWidgetBottom-edited');
$this->assertEquals($sideWidgets[0]->getTitle(), 'MyTestWidgetSide-edited');
}
public function testEditAWidgetFromOneAreaAndDeleteAWidgetFromAnotherArea()
{
// First get some widgets in there
@ -394,13 +439,18 @@ class WidgetAreaEditorTest extends SapphireTest
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$request = new HTTPRequest('get', 'post', array(), $data);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(new ContentController(), 'Form', new FieldList($editorSide, $editorBott), new FieldList());
$form = new Form(
new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$form->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
$page = new FakePage();
$editorSide->saveInto($page);
$editorBott->saveInto($page);
@ -410,7 +460,7 @@ class WidgetAreaEditorTest extends SapphireTest
$page->SideBar()->flushCache();
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
// Save again (after removing the SideBar's widget)
$data = array(
'Widget' => array(
@ -425,7 +475,7 @@ class WidgetAreaEditorTest extends SapphireTest
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$request = new HTTPRequest('get', 'post', array(), $data);
$form->setRequest($request);
$form->saveInto($page);
@ -435,23 +485,9 @@ class WidgetAreaEditorTest extends SapphireTest
$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');
$this->assertEquals($sideWidgets[0]->getTitle(), 'MyTestWidgetSide-edited');
}
}
class WidgetAreaEditorTest_FakePage extends Page implements TestOnly
{
private static $has_one = array(
"BottomBar" => "WidgetArea",
);
}
class WidgetAreaEditorTest_TestWidget extends Widget implements TestOnly
{
private static $cmsTitle = "Test widget";
private static $title = "Test widget";
private static $description = "Test widget";
}

View File

@ -0,0 +1,16 @@
<?php
namespace SilverStripe\Widgets\Tests\WidgetAreaEditorTest;
use Page;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Widgets\Model\WidgetArea;
class FakePage extends Page implements TestOnly
{
private static $table_name = 'FakePage';
private static $has_one = array(
"BottomBar" => WidgetArea::class
);
}

View File

@ -0,0 +1,14 @@
<?php
namespace SilverStripe\Widgets\Tests\WidgetAreaEditorTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Widgets\Model\Widget;
class TestWidget extends Widget implements TestOnly
{
private static $table_name = 'TestWidget';
private static $cmsTitle = "Test widget";
private static $title = "Test widget";
private static $description = "Test widget";
}

View File

@ -1,4 +1,18 @@
<?php
namespace SilverStripe\Widgets\Tests;
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\Widgets\Model\Widget;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\Form;
use SilverStripe\Widgets\Controllers\WidgetController;
use SilverStripe\Widgets\Tests\WidgetControllerTest\TestPage;
use SilverStripe\Widgets\Tests\WidgetControllerTest\TestWidget;
/**
* @package widgets
* @subpackage tests
@ -8,19 +22,19 @@ class WidgetControllerTest extends FunctionalTest
protected static $fixture_file = 'WidgetControllerTest.yml';
protected $extraDataObjects = array(
'WidgetControllerTestPage',
'WidgetControllerTest_Widget',
TestPage::class,
TestWidget::class,
);
public function testWidgetFormRendering()
{
$page = $this->objFromFixture('WidgetControllerTestPage', 'page1');
$page->publish('Stage', 'Live');
$widget = $this->objFromFixture('WidgetControllerTest_Widget', 'widget1');
$page = $this->objFromFixture(TestPage::class, 'page1');
$page->copyVersionToStage('Stage', 'Live');
$widget = $this->objFromFixture(TestWidget::class, 'widget1');
$response = $this->get($page->URLSegment);
$formAction = sprintf('%s/widget/%d/Form', $page->URLSegment, $widget->ID);
$this->assertContains(
$formAction,
@ -28,16 +42,16 @@ class WidgetControllerTest extends FunctionalTest
"Widget forms are rendered through WidgetArea templates"
);
}
public function testWidgetFormSubmission()
{
$page = $this->objFromFixture('WidgetControllerTestPage', 'page1');
$page->publish('Stage', 'Live');
$widget = $this->objFromFixture('WidgetControllerTest_Widget', 'widget1');
$page = $this->objFromFixture(TestPage::class, 'page1');
$page->copyVersionToStage('Stage', 'Live');
$widget = $this->objFromFixture(TestWidget::class, 'widget1');
$response = $this->get($page->URLSegment);
$response = $this->submitForm('Form_Form', null, array('TestValue'=>'Updated'));
$response = $this->submitForm('Form_Form', null, array('TestValue' => 'Updated'));
$this->assertContains(
'TestValue: Updated',
@ -51,49 +65,3 @@ class WidgetControllerTest extends FunctionalTest
);
}
}
/**
* @package widgets
* @subpackage tests
*/
class WidgetControllerTest_Widget extends Widget implements TestOnly
{
private static $db = array(
'TestValue' => 'Text'
);
}
/**
* @package widgets
* @subpackage tests
*/
class WidgetControllerTest_WidgetController extends WidgetController implements TestOnly
{
private static $allowed_actions = array(
'Form'
);
public function Form()
{
$widgetform = new Form(
$this,
'Form',
new FieldList(
new TextField('TestValue')
),
new FieldList(
new FormAction('doAction')
)
);
return $widgetform;
}
public function doAction($data, $form)
{
return sprintf('TestValue: %s\nWidget ID: %d',
$data['TestValue'],
$this->widget->ID
);
}
}

View File

@ -1,10 +1,10 @@
WidgetControllerTest_Widget:
SilverStripe\Widgets\Tests\WidgetControllerTest\TestWidget:
widget1:
Title: Widget 1
WidgetArea:
SilverStripe\Widgets\Model\WidgetArea:
area1:
Widgets: =>WidgetControllerTest_Widget.widget1
WidgetControllerTestPage:
Widgets: =>SilverStripe\Widgets\Tests\WidgetControllerTest\TestWidget.widget1
SilverStripe\Widgets\Tests\WidgetControllerTest\TestPage:
page1:
Title: Page1
WidgetControllerTestSidebar: =>WidgetArea.area1
WidgetControllerTestSidebar: =>SilverStripe\Widgets\Model\WidgetArea.area1

View File

@ -0,0 +1,21 @@
<?php
namespace SilverStripe\Widgets\Tests\WidgetControllerTest;
use Page;
use SilverStripe\Dev\TestOnly;
use SilverStripe\View\SSViewer;
use SilverStripe\Widgets\Model\WidgetArea;
/**
* @package cms
* @subpackage tests
*/
class TestPage extends Page implements TestOnly
{
private static $table_name = 'TestPage';
private static $has_one = array(
'WidgetControllerTestSidebar' => WidgetArea::class
);
}

View File

@ -0,0 +1,25 @@
<?php
namespace SilverStripe\Widgets\Tests\WidgetControllerTest;
use PageController;
use ReflectionClass;
use SilverStripe\Dev\TestOnly;
use SilverStripe\View\SSViewer;
use SilverStripe\Widgets\Tests\WidgetControllerTest\TestPage;
/**
* @package cms
* @subpackage tests
*/
class TestPageController extends PageController implements TestOnly
{
/**
* Template selection doesnt work in test folders, so we add a test theme a template name.
*/
public function getViewer($action)
{
SSViewer::add_themes(["silverstripe/widgets:widgets/tests/WidgetControllerTest"]);
return new SSViewer(TestPage::class);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace SilverStripe\Widgets\Tests\WidgetControllerTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Widgets\Model\Widget;
/**
* @package widgets
* @subpackage tests
*/
class TestWidget extends Widget implements TestOnly
{
private static $table_name = 'TestWidgetB';
private static $db = array(
'TestValue' => 'Text'
);
}

View File

@ -0,0 +1,46 @@
<?php
namespace SilverStripe\Widgets\Tests\WidgetControllerTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\TextField;
use SilverStripe\Widgets\Controllers\WidgetController;
/**
* @package widgets
* @subpackage tests
*/
class TestWidgetController extends WidgetController implements TestOnly
{
private static $allowed_actions = array(
'Form'
);
public function Form()
{
$widgetform = new Form(
$this,
'Form',
new FieldList(
new TextField('TestValue')
),
new FieldList(
new FormAction('doAction')
)
);
return $widgetform;
}
public function doAction($data, $form)
{
return sprintf(
'TestValue: %s\nWidget ID: %d',
$data['TestValue'],
$this->widget->ID
);
}
}

View File

@ -0,0 +1 @@
$WidgetControllerTestSidebar

View File

@ -1,29 +0,0 @@
<?php
/**
* @package cms
* @subpackage tests
*/
class WidgetControllerTestPage extends Page implements TestOnly
{
private 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.
*/
public function getViewer($action)
{
$templates = array('WidgetControllerTestPage');
return new SSViewer($templates);
}
}

View File

@ -1 +0,0 @@
$WidgetControllerTestSidebar

View File

@ -1 +0,0 @@
$Form