Compare commits

...

183 Commits
1.1.2 ... 2

Author SHA1 Message Date
Guy Sartorelli eacb02c297
Merge branch '2.4' into 2 2023-08-30 10:23:04 +12:00
Guy Sartorelli afe1cc5da0
ENH Update translations (#201) 2023-08-21 13:19:54 +12:00
Guy Sartorelli f2b92e8f83
Merge branch '2.4' into 2 2023-04-26 12:46:00 +12:00
Guy Sartorelli 2b53f5095c
MNT Revert erroneous dependency changes (#198) 2023-03-28 17:02:24 +13:00
Maxime Rainville d27f656e97
Merge pull request #197 from creative-commoners/pulls/2/dispatch-ci
MNT Use gha-dispatch-ci
2023-03-23 14:19:31 +13:00
Steve Boyd b615184c54 MNT Use gha-dispatch-ci 2023-03-21 13:42:53 +13:00
Guy Sartorelli a9a20ab1c0
MNT Update development dependencies 2023-03-10 16:32:47 +13:00
Guy Sartorelli 8ad0537473
MNT Update release dependencies 2023-03-10 16:32:44 +13:00
Guy Sartorelli 02d5a17e1f
MNT Update development dependencies 2023-03-10 12:21:29 +13:00
Guy Sartorelli 53e7646700
Merge pull request #196 from creative-commoners/pulls/2/tx-1678080282
ENH Update translations
2023-03-08 10:32:31 +13:00
Steve Boyd 812f3dc282 ENH Update translations 2023-03-06 18:24:42 +13:00
Steve Boyd 9934adecdb Merge branch '2.3' into 2 2022-08-02 19:02:13 +12:00
Steve Boyd 08cdad1290 Merge branch '2.2' into 2.3 2022-08-02 19:02:09 +12:00
Guy Sartorelli 08554e7882
Merge pull request #192 from creative-commoners/pulls/2.2/standardise-modules
MNT Standardise modules
2022-08-02 15:50:59 +12:00
Steve Boyd 57b1d87577 MNT Standardise modules 2022-08-01 16:23:55 +12:00
Steve Boyd 3fd8120b74 Merge branch '2.3' into 2 2022-07-25 11:50:19 +12:00
Steve Boyd d907c77ed6 Merge branch '2.2' into 2.3 2022-07-25 11:50:15 +12:00
Guy Sartorelli 65ef1bbc50
Merge pull request #191 from creative-commoners/pulls/2.2/module-standards
MNT Use GitHub Actions CI
2022-07-15 11:46:04 +12:00
Steve Boyd d16e4770da MNT Use GitHub Actions CI 2022-07-05 19:10:19 +12:00
Steve Boyd 3685869917 Merge branch '2.3' into 2 2022-05-10 22:11:42 +12:00
Steve Boyd 3d89a29628 Update translations 2022-05-04 13:29:41 +12:00
Guy Sartorelli d78326987c
Merge pull request #189 from creative-commoners/pulls/2/php81
ENH PHP 8.1 compatibility
2022-04-26 17:58:46 +12:00
Steve Boyd 51d29b3d5a ENH PHP 8.1 compatibility 2022-04-13 13:54:56 +12:00
Maxime Rainville 5aca443a7c
Merge pull request #187 from creative-commoners/pulls/2/sapphire-test-nine
API phpunit 9 support
2021-11-10 13:19:21 +13:00
Steve Boyd 2b9536e5ae
DEP Use phpunit ^9.5 2021-11-10 10:47:55 +13:00
Steve Boyd a08aa08f4d API phpunit 9 support 2021-10-21 13:14:57 +13:00
Maxime Rainville 6c2b654f19 Merge branch '2.1' into 2 2021-06-02 15:33:46 +12:00
Maxime Rainville 6e163beb44
Merge pull request #186 from creative-commoners/pulls/2.1/revert-translations
FIX Add namespaces back to translation files
2021-06-01 14:25:13 +12:00
Steve Boyd d4ae7ff9ab FIX Add namespaces back to translation files 2021-05-31 10:57:28 +12:00
Steve Boyd 92f90fbea5 Merge branch '2.1' into 2 2021-05-21 14:32:28 +12:00
Maxime Rainville f759a8b56b Update translations 2021-05-17 15:09:54 +12:00
Maxime Rainville 81b59ab4b9 MNT Remove obsolete branch-alias 2021-05-05 11:17:32 +12:00
Steve Boyd 0f37c09531
Update build status badge 2021-01-21 16:43:31 +13:00
Steve Boyd c3fc2cab68 Merge branch '2.0' into 2 2020-11-11 17:47:14 +13:00
Serge Latyntsev 0fda5eec16
Merge pull request #185 from creative-commoners/pulls/2.0/shared-config
MNT Use shared travis config, use sminnee/phpunit
2020-11-10 16:37:35 +13:00
Steve Boyd 1265ce99a6 MNT Use shared travis config, use sminnee/phpunit 2020-11-10 14:08:17 +13:00
Garion Herman 8db05ea5f0 Merge branch '2.0' into 2 2019-11-18 14:39:24 +13:00
Serge Latyntsev 007b6fcdfa
Merge pull request #181 from creative-commoners/pulls/2.0/update-travis-config
Update and expand Travis config
2019-11-18 14:35:17 +13:00
Garion Herman 42af10671e FIX Fetch source so that PHPCS config is available 2019-11-18 14:20:09 +13:00
Garion Herman f396ee7242 Update and expand Travis config 2019-11-18 14:00:27 +13:00
Robbie Averill ade8047461
Merge pull request #177 from wernerkrauss/patch-1
Fix broken template paths in $Content
2018-10-16 15:29:38 +02:00
wernerkrauss 5d2f0570d8
Fix broken template paths in $Content
Use framework logic to get the templates of the current Widget Subclass
2018-10-16 15:12:34 +02:00
Robbie Averill 7d2dd46159
Merge pull request #170 from creative-commoners/pulls/master/add-supported-module-badge
Add supported module badge to readme
2018-06-19 09:24:08 +12:00
Dylan Wagstaff 41c9ecffe6 Add supported module badge to readme 2018-06-15 17:52:55 +12:00
Robbie Averill 35df96f502 Merge branch '2.0' 2018-06-11 12:09:57 +12:00
Robbie Averill 6ce962607f Remove obsolete branch alias 2018-06-11 12:09:26 +12:00
Robbie Averill eb81b3c8d9
Merge pull request #167 from silverstripe/raissanorth-patch-1
Add missing section to README.md
2018-03-26 10:54:06 +13:00
Raissa North 129a650729
Add missing section to README.md
Comparing the README to the Getting Started section of the user docs, it appears that there is a section mission. This has been added back in.
2018-03-26 10:50:16 +13:00
Dylan Wagstaff d17c577609
Merge pull request #166 from spaceship-operators/widget-area-ss4-styling
Update widgetarea styling to be inline with SS4
2018-03-15 15:08:25 +13:00
Scott Hutchinson 294f9b60b5 Update widgetarea styling to be inline with SS4 2018-03-15 14:44:10 +13:00
Robbie Averill 76b8b3f891
Merge pull request #164 from creative-commoners/pulls/2.0/return-null-for-empty-null
API Remove overriding function getTitle()
2018-02-02 11:53:16 +13:00
Raissa North ab8349209a API Remove overriding function getTitle()
In the case that no title is entered in the CMS's title field no title is returned, rather than the CMS widget's title.
2018-02-02 11:13:52 +13:00
Dylan Wagstaff 0a3c280c3d
Merge pull request #163 from creative-commoners/pulls/2.0/ownership-api
API Widgets and WidgetAreas are versioned and are owned by pages
2017-12-22 11:13:55 +13:00
Robbie Averill 8583b894a1 FIX Update tests to ensure TestPage owns its widget area 2017-12-21 11:16:48 +13:00
Robbie Averill 5ca288c9a3 FIX Use full versioning for Widget and WidgetArea to preserve draft state between page publishes 2017-12-21 10:45:55 +13:00
Robbie Averill e1a0469c46 API Widgets and WidgetAreas are versioned and are owned by pages 2017-12-19 16:01:52 +13:00
Robbie Averill a0b1f9b1cf
Merge pull request #162 from creative-commoners/pulls/2.0/update-to-ss4
Check module for SS4 compatibility
2017-12-19 16:01:49 +13:00
Robbie Averill a2f4594597 FIX Update legacy translation tags in templates, run text collector, update other langs 2017-12-19 14:33:16 +13:00
Raissa North de7473e9ea FIX Update translations 2017-12-19 10:28:59 +13:00
Raissa North 58e26969a3 FIX Update documentation and turn into vendor module 2017-12-19 10:25:38 +13:00
Raissa North 285b46fafb FIX Add and optimise namespaces 2017-12-19 10:25:38 +13:00
Raissa North 0916970951 ENHANCEMENT Add codesniffer ruleset 2017-12-19 10:25:38 +13:00
Robbie Averill 5287ebcbe5 Merge branch '1' 2017-12-19 10:21:48 +13:00
Robbie Averill c5511726ef Update branch alias for 1.x-dev 2017-12-19 10:19:48 +13:00
Robbie Averill 4dccc49a41 Merge branch '1.2' into 1.3 2017-12-19 10:18:34 +13:00
Robbie Averill 646cd1a210 Merge branch '1.1' into 1.2 2017-12-19 10:18:24 +13:00
Robbie Averill 7767292d50 Merge branch '1.0' into 1.1 2017-12-19 10:17:43 +13:00
Robbie Averill c28c3c1d8d Remove Transifex configuration. Commit directly to lang files for SS3. 2017-12-19 10:17:25 +13:00
Dylan Wagstaff f8f10e96ff
Merge pull request #161 from creative-commoners/pulls/2.0/fix-namespace-handling
FIX Ensure namespaced widget classes are handled and Widget is not included
2017-12-14 14:48:54 +13:00
Robbie Averill bf9fbe3aa7 FIX Ensure namespaced widget classes are handled and Widget is not included 2017-12-13 23:03:12 +13:00
Robbie Averill 406f925beb
FIX Scrutinizer configuration uses new build system 2017-11-21 13:55:58 +13:00
Robbie Averill 712fb56897
Merge pull request #158 from adrian-stein/master
Add in missing requirements
2017-11-21 13:55:05 +13:00
Adrian Stein e9e30b6489 Add in missing requirements 2017-11-21 11:28:55 +11:00
Damian Mooyman 023eef23c6 Merge pull request #156 from creative-commoners/pulls/2.0/vendorise
NEW Install module to the vendor folder
2017-10-11 11:51:36 +13:00
Robbie Averill fb91acbe3a NEW Install module to the vendor folder 2017-10-11 09:54:52 +13:00
Robbie Averill 3f02a0b295 Merge pull request #155 from creative-commoners/pulls/vendorise-ci
FIX convert CI bootstrap references to new their new locations in vendor
2017-10-05 16:02:05 +13:00
Dylan Wagstaff 18296d00e4 Update template path to allow PageController test to pass 2017-10-05 15:26:52 +13:00
Dylan Wagstaff 36a5840d45 FIX convert CI bootstrap references to new their new locations in vendor 2017-10-05 10:09:49 +13:00
Franco Springveldt 26f1c2bd74 Merge pull request #153 from robbieaverill/pulls/2.0/update-travis
FIX Update Travis configuration to be standalone, add codecov.io
2017-09-14 14:27:49 +12:00
Robbie Averill 9b12b602f5 FIX Use ModuleLoader for requirement resolution and typo in exception in Widget::getController 2017-09-14 14:16:45 +12:00
Robbie Averill 7c85b1b5d9 FIX Update tests for removed core features, use Controller for requests instead of Forms, etc 2017-09-14 11:15:01 +12:00
Robbie Averill ad6a965526 FIX Update Travis configuration to be standalone, add codecov.io 2017-08-25 16:29:09 +12:00
Robbie Averill 33f173d39b Merge pull request #152 from pine3ree/patch-1
use already fetched value
2017-08-25 16:20:18 +12:00
maks feltrin 33ef8b9d2c
use already fetched values 2017-07-19 15:23:30 +01:00
Daniel Hensby 5437f6b2a5 Merge pull request #149 from creative-commoners/pulls/2.0/remove-php5.5
Remove PHP 5.5 from SS4 compat build matrix
2017-06-15 16:58:28 +01:00
Daniel Hensby cc7d188927 Merge pull request #150 from creative-commoners/pulls/1.3/travis-php7
Add PHP7 + SS3.6 build to Travis configuration
2017-06-15 16:57:51 +01:00
Daniel Hensby 4c87ff3896 Merge pull request #146 from rasstislav/patch-1
added missing _t() function
2017-06-15 16:53:10 +01:00
Rastislav Brandobur a434245cfa added WidgetPageExtension.INHERITSIDEBAR definition to lang subfolder 2017-06-15 01:54:19 +02:00
Robbie Averill 8f0427922b Add PHP7 + SS3.6 build to Travis configuration 2017-06-15 11:49:57 +12:00
Robbie Averill 9aa40a0611 Remove PHP 5.5 from SS4 compat build matrix 2017-06-15 11:47:47 +12:00
Rastislav Brandobur 80029afafb added missing _t() function 2017-04-06 11:01:33 +02:00
Brett Tasker df4844d45f Update translations 2017-02-09 11:28:52 +13:00
Damian Mooyman 1fe4c3db70 Merge pull request #143 from robbieaverill/feature/ss4-compat
Enhancement: SilverStripe 4 compatibility
2017-01-26 17:42:32 +13:00
Robbie Averill 8dae0a6f95 Tabs to spaces in Controller 2017-01-26 08:25:53 +13:00
Robbie Averill ec9421ba65 FIX Add legacy class remapping, escape FQ class names in URLs, docs and add bootstrap to buttons 2017-01-26 08:25:10 +13:00
Robbie Averill e7de0c61bf DOCS Add all build badges to readme 2017-01-26 08:22:02 +13:00
Robbie Averill 8eee397b9a API Implement namespacing, add upgrade mapping. Remove deprecated methods and support for Widget_Controller (underscored) 2017-01-26 08:22:02 +13:00
Robbie Averill defb4bf134 SS4 compat: Update readme, docs, composer configuration and Travis build configuration 2017-01-26 08:12:04 +13:00
Daniel Hensby f3b14df4e4
Merge branch '1.3' 2017-01-25 13:33:02 +00:00
Daniel Hensby ef40ca30aa
Merge branch '1.2' into 1.3 2017-01-25 13:31:44 +00:00
Daniel Hensby 1bbaba52e0
Merge branch '1.1' into 1.2 2017-01-25 13:30:01 +00:00
Daniel Hensby 616bcdb364
Merge branch '1.0' into 1.1 2017-01-25 13:24:17 +00:00
Daniel Hensby 8c705392e9
Updating constraint to exclude SS4 as supported 2017-01-25 13:20:55 +00:00
Daniel Hensby 661e7ee48a
Alias master as 2.0.x 2017-01-25 13:18:05 +00:00
Daniel Hensby cc591f2c56
Removing branch alias 2017-01-25 13:16:43 +00:00
Daniel Hensby 40af14906d Merge pull request #140 from Cheddam/patch-1
Add documentation for limiting allowed widgets
2016-12-28 11:30:17 +00:00
Garion Herman 04dbb41125 Add documentation for limiting allowed widgets
An addendum to clarify that yes, you can lock a WidgetArea down to certain widgets, and how to accomplish this.
2016-12-20 23:44:49 +13:00
Damian Mooyman 6dd4ff8525 Update translations 2016-11-17 12:39:49 +13:00
Damian Mooyman 58aa2a6a35 Remove obsolete branch-alias 2016-11-17 10:17:57 +13:00
Daniel Hensby 0986537be7 Merge pull request #135 from rasstislav/patch-1
Update Widget.php
2016-09-07 16:12:27 +01:00
rasstislav 86bf12a181 Update Widget.php 2016-09-07 16:58:24 +02:00
Damian Mooyman 8d5203c08a Update translations 2016-08-17 11:08:01 +12:00
Daniel Hensby cbebc589b2 Merge pull request #134 from torleif/patch-1
FIX: use correct ID if it exists. Fixes #133
2016-07-18 00:44:14 +01:00
torleif 3bc304b844 FIX: use correct ID if it exists. Fixes #133 2016-07-18 11:12:46 +12:00
Daniel Hensby 36ccdf2ebc
Merge pull request #128 from torleif/patch-2 2016-07-14 00:32:45 +01:00
torleif 987d29341e Fix: #127 - generate unique ID upon creation
Fixes front end components that require a unique ID. Duplicate IDs in the DOM is invalid HTML.

Fix: #127 - code formatting

Check validation before writing

BUG fixes duplicate ID issue Fixes #127

BUG fixes duplicate ID issue Fixes #127

remove unneeded write()

Fix: #127 - generate unique ID upon creation
2016-07-14 10:20:23 +12:00
Damian Mooyman 9e2b4f1586 Merge pull request #130 from torleif/patch-3
#129 - FIX: htmlfield editor will be created correctly
2016-07-12 14:44:18 +12:00
torleif e6ea99ac12 #129 - FIX: htmlfield editor will be created correctly 2016-06-21 15:44:37 +12:00
Stevie Mayhew 4aa66af42d Merge pull request #126 from torleif/patch-1
#125 - FIX: disconnect tinyMCE while dragging
2016-06-21 13:20:39 +12:00
torleif 92e3fd6d9a #125 - FIX: firefox support for disconnecting tinyMCE 2016-06-21 13:17:30 +12:00
torleif 1719ee3248 #125 - FIX: disconnect tinyMCE while dragging
By disconnecting the tinyMCE it won't trigger a recreate after drag and dropping, so you won't wind up with multiple htmleditors.
2016-06-14 10:21:58 +12:00
Damian Mooyman 90d6402185 Update changelog for 1.2.2 2016-02-04 18:19:51 +13:00
Damian Mooyman 04131d5b09 Merge pull request #117 from steiha/patch-1
ItemsToRender might return NULL // avoid warnings
2016-02-04 18:15:05 +13:00
Damian Mooyman 9aac12ab66 Update translations 2016-02-04 11:03:42 +13:00
Daniel Hensby 5ef0ef34a7 Merge pull request #120 from silverstripe/tractorcow-patch-1
Fix readme.md links
2016-01-26 09:16:31 +00:00
Damian Mooyman 9026821f29 Fix readme.md links
Resolves #119
2016-01-25 17:05:03 +13:00
Damian Mooyman 783f41dffa Merge pull request #118 from helpfulrobot/update-license-year
Updated license year
2016-01-05 11:14:46 +13:00
helpfulrobot cd434c3e25 Updated license year 2016-01-01 06:39:27 +13:00
Stefan Eickhoff 65b72e6474 ItemsToRender might return NULL // avoid warnings 2015-12-06 01:04:35 +01:00
Daniel Hensby db6500c3b0 Merge pull request #116 from helpfulrobot/add-standard-code-of-conduct
Added standard code of conduct
2015-11-21 12:28:19 +00:00
helpfulrobot c53a1d5931 Added standard code of conduct 2015-11-21 20:18:29 +13:00
Daniel Hensby 79f5ebb167 Merge pull request #115 from tractorcow/pulls/fix-tests-2
Remove invalid 3.1 test fixture
2015-11-19 20:46:36 +00:00
Damian Mooyman 7d0e8f2bd0 Remove invalid 3.1 test fixture 2015-11-20 09:40:06 +13:00
Daniel Hensby 453ce12c2d Merge pull request #114 from helpfulrobot/add-standard-git-attributes
Added standard git attributes
2015-11-19 17:35:15 +00:00
Daniel Hensby dfe1236eef Merge pull request #107 from helpfulrobot/convert-to-psr-2
Converted to PSR-2
2015-11-19 17:34:49 +00:00
Daniel Hensby a77ba66858 Merge pull request #110 from helpfulrobot/add-standard-travis-config
Added standard Travis config
2015-11-19 17:32:45 +00:00
Daniel Hensby 712a655cd8 Merge pull request #112 from helpfulrobot/add-standard-license
Added standard license
2015-11-19 12:04:02 +00:00
Daniel Hensby 3e217a8d69 Merge pull request #113 from helpfulrobot/add-license-to-composer
Added license to composer.json
2015-11-19 11:59:51 +00:00
Daniel Hensby 5b8574eedd Merge pull request #109 from helpfulrobot/add-standard-editor-config
Added standard editor config
2015-11-19 09:30:08 +00:00
helpfulrobot 085d8f881f Added standard git attributes 2015-11-19 19:14:35 +13:00
helpfulrobot 0fce21beed Added license to composer.json 2015-11-19 18:53:43 +13:00
helpfulrobot 08a2b444cf Added standard license 2015-11-19 18:33:14 +13:00
Scott Hutchinson ecce5327a8 Merge pull request #111 from tractorcow/pulls/release-121
Update changelog for 1.2.1
2015-11-19 16:58:46 +13:00
Damian Mooyman 6104f17220 Update changelog for 1.2.1 2015-11-19 15:46:28 +13:00
helpfulrobot cc836f3be4 Added standard Travis config 2015-11-19 14:21:30 +13:00
helpfulrobot 6e9e3c9aaa Added standard editor config 2015-11-19 13:27:37 +13:00
Daniel Hensby 9fcc75b4d6 Merge pull request #108 from helpfulrobot/add-standard-travis-config
Added standard Travis config
2015-11-18 23:40:21 +00:00
Daniel Hensby dbafc880a3 Merge pull request #106 from helpfulrobot/add-standard-scrutinizer-config
Added standard Scrutinizer config
2015-11-18 23:40:01 +00:00
helpfulrobot 2dd717cb84 Added standard Travis config 2015-11-18 17:24:16 +13:00
helpfulrobot 130e9d06a1 Converted to PSR-2 2015-11-18 17:08:21 +13:00
helpfulrobot b41ad4cf71 Added standard Scrutinizer config 2015-11-18 15:42:00 +13:00
Damian Mooyman d66a641934 Update translations 2015-11-17 16:07:38 +13:00
Damian Mooyman 799c2f1bf0 Merge pull request #105 from dnadesign/1.1
Making widgets able to be used in a non page controller
2015-11-11 16:24:08 +13:00
John Milmine 0f207180b4 Making widgets able to be used in a non page controller 2015-11-11 15:01:03 +13:00
Damian Mooyman 2fe849365f Merge pull request #100 from shoaibali/changelog
OSS-905 Added changelog
2015-11-11 13:57:23 +13:00
ezero ce9095cf71 Removed unreleased from changelog 2015-11-09 17:25:30 +13:00
Christopher Pitt ae90bb1591 Merge pull request #98 from shoaibali/attributes
OSS-905 Added gitattributes
2015-11-09 16:36:54 +13:00
Christopher Pitt f17f56acd3 Merge pull request #102 from shoaibali/movedocs
OSS-905 Moved docs to docs/en/
2015-11-09 16:35:48 +13:00
Christopher Pitt bf4d36d40b Merge pull request #103 from shoaibali/contributing
OSS-905 Moved contributions of Widgets to contributing.md
2015-11-09 16:32:16 +13:00
Shoaib Ali a5b37aa204 Merge branch 'master' of https://github.com/silverstripe/silverstripe-widgets into movedocs 2015-11-07 14:19:06 +13:00
Shoaib Ali ed56b29fe8 OSS-905 Added docs to gitattributes 2015-11-07 12:56:53 +13:00
Shoaib Ali 6136b4f1ea OSS-905 Moved contributions of Widgets to contributing.md 2015-11-07 12:49:46 +13:00
Shoaib Ali 4e5f1bf3e2 OSS-905 Moved docs to docs/en/ 2015-11-07 12:45:35 +13:00
Christopher Pitt 705367eb32 Merge pull request #101 from shoaibali/codeofconduct
OSS-905 Added code of conduct
2015-11-07 12:34:21 +13:00
Shoaib Ali 9cd3837dbc OSS-905 Added code of conduct 2015-11-07 12:28:34 +13:00
Shoaib Ali 210ffc9645 OSS-905 Added changelog 2015-11-07 12:17:25 +13:00
Christopher Pitt 936cb63826 Merge pull request #99 from shoaibali/contributing
OSS-905 Moved contributing section to contributing.md
2015-11-07 12:04:58 +13:00
Shoaib Ali a586a034d1 OSS-905 Moved contributing section to contributing.md 2015-11-07 12:03:05 +13:00
Shoaib Ali a397764ad7 OSS-905 Added gitattributes 2015-11-07 11:48:19 +13:00
Cam Findlay c202259bc1 Merge pull request #96 from assertchris/add-scrutinizer-support
Added Scrutinizer support
2015-11-07 11:42:11 +13:00
Christopher Pitt ff39cd6eb4 Added Scrutinizer support 2015-11-07 11:07:39 +13:00
Christopher Pitt eee39bca4e Merge pull request #95 from tractorcow/pulls/fix-tests
BUG Prevent non-widget tests from loading WidgetAreaEditorTest_FakePage every query
2015-11-05 10:14:23 +13:00
Damian Mooyman 79db693835 BUG Prevent non-widget tests from loading WidgetAreaEditorTest_FakePage every query 2015-11-04 17:01:46 +13:00
Scott Hutchinson c16db15e9e Merge pull request #94 from tractorcow/pulls/tests
Tests for php 5.6 and framework 3.2
2015-10-30 18:44:59 +13:00
Damian Mooyman ca1bb3d037 Tests for php 5.6 and framework 3.2 2015-10-30 18:11:46 +13:00
☃ Stephen Shkardoon ☃ 17c94ff9ce Merge pull request #93 from hailwood/patch-1
Support widget forms with InheritSidebar
2015-09-22 13:44:40 +12:00
Matthew Hailwood ff0178330a Support widget forms with InheritSidebar
Currently the system assumes that if a widget ends up at "handleWidget" that the widget resides on the current page. This patch checks if we are inheriting the sidebar and if so finds the top level page with the widget.
2015-09-22 13:42:20 +12:00
Christopher Pitt 791d32a303 Merge pull request #89 from tractorcow/pulls/1.0/fix-cms-fields-bug
BUG Fix incorrect extension variable
2015-05-14 11:19:49 +12:00
Damian Mooyman ed6114a7dc BUG Fix incorrect extension variable
Fixes silverstripe/silverstripe-blog#233
2015-05-14 10:50:33 +12:00
Christopher Pitt cdfec4c7dc Merge pull request #87 from tractorcow/pulls/1.2
Move 3.2+ compat version to 1.2
2015-05-04 12:46:52 +12:00
Damian Mooyman ed8f7d867a Move 3.2+ compat version to 1.2
Fix version constraint
2015-05-04 12:03:29 +12:00
Ingo Schommer d0ce44401f 4.x compat, updated Travis build targets 2015-04-30 22:52:09 +12:00
Ingo Schommer f2f6b6e18c 3.2.x compat 2015-04-30 22:49:17 +12:00
81 changed files with 2764 additions and 1837 deletions

View File

@ -1,24 +1,17 @@
# For more information about the properties used in
# this file, please see the EditorConfig documentation:
# http://editorconfig.org/
# For more information about the properties used in this file,
# please see the EditorConfig documentation:
# http://editorconfig.org
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = tab
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.yml]
[{*.yml,package.json}]
indent_size = 2
indent_style = space
[{.travis.yml,package.json}]
# The indent size used in the `package.json` file cannot be changed
# The indent size used in the package.json file cannot be changed:
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
indent_size = 2
indent_style = space

7
.gitattributes vendored Normal file
View File

@ -0,0 +1,7 @@
/tests export-ignore
/docs export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/.scrutinizer.yml export-ignore
/codecov.yml export-ignore

11
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,11 @@
name: CI
on:
push:
pull_request:
workflow_dispatch:
jobs:
ci:
name: CI
uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1

16
.github/workflows/dispatch-ci.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Dispatch CI
on:
# At 1:00 PM UTC, only on Saturday and Sunday
schedule:
- cron: '0 13 * * 6,0'
jobs:
dispatch-ci:
name: Dispatch CI
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Dispatch CI
uses: silverstripe/gha-dispatch-ci@v1

17
.github/workflows/keepalive.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: Keepalive
on:
workflow_dispatch:
# The 4th of every month at 10:50am UTC
schedule:
- cron: '50 10 4 * *'
jobs:
keepalive:
name: Keepalive
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Keepalive
uses: silverstripe/gha-keepalive@v1

View File

@ -1,23 +0,0 @@
# See https://github.com/silverstripe-labs/silverstripe-travis-support for setup details
language: php
php:
- 5.3
sudo: false
env:
- DB=MYSQL CORE_RELEASE=3.1
matrix:
include:
- php: 5.4
env: DB=PGSQL CORE_RELEASE=3.1
before_script:
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support
- php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss
- cd ~/builds/ss
script:
- vendor/bin/phpunit widgets/tests/

View File

@ -1,8 +1,9 @@
[main]
host = https://www.transifex.com
[silverstripe-widgets.master]
[o:silverstripe:p:silverstripe-widgets:r:master]
file_filter = lang/<lang>.yml
source_file = lang/en.yml
source_lang = en
type = YML
type = YML

7
.upgrade.yml Normal file
View File

@ -0,0 +1,7 @@
mappings:
WidgetContentControllerExtension: SilverStripe\Widgets\Controllers\WidgetContentControllerExtension
Widget_Controller: SilverStripe\Widgets\Models\WidgetController
WidgetPageExtension: SilverStripe\Widgets\Extensions\WidgetPageExtension
WidgetAreaEditor: SilverStripe\Widgets\Forms\WidgetAreaEditor
Widget: SilverStripe\Widgets\Model\Widget
WidgetArea: SilverStripe\Widgets\Model\WidgetArea

24
LICENSE
View File

@ -1,24 +0,0 @@
* 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.

434
README.md
View File

@ -1,34 +1,38 @@
# Widgets Module
[![Build Status](https://secure.travis-ci.org/silverstripe/silverstripe-widgets.png?branch=1.1)](http://travis-ci.org/silverstripe/silverstripe-widgets)
[![CI](https://github.com/silverstripe/silverstripe-widgets/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/silverstripe-widgets/actions/workflows/ci.yml)
[![Silverstripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/)
## Introduction
## Overview
[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/)
Widgets are small pieces of functionality such as showing the latest comments or Flickr photos. They normally display on
the sidebar of your website.
## Requirements
* SilverStripe 3.1
* Silverstripe 4.0
**Note:** this version is compatible with Silverstripe 4. For Silverstripe 3, please see [the 1.x release line](https://github.com/silverstripe/silverstripe-widgets/tree/1.3).
### Installation
Install the module through [composer](http://getcomposer.org):
composer require silverstripe/widgets
```
$ 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
way to get started would be to create a single collection of widgets under the
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
@ -36,43 +40,60 @@ 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
By following the "Packaging" rules below, widgets are easily installed. This example uses the Blog module which by default has widgets already enabled.
* Install the [blog module](http://www.silverstripe.org/blog-module/).
* Download the widget and unzip to the main folder of your SilverStripe website, e.g. to `/widget_<widget-name>/`. The folder
* Install the [blog module](https://github.com/silverstripe/silverstripe-blog/).
* Download the widget 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 click the new widget to activate it.
* Your blog will now have the widget shown
## Documentation
### Adding widgets to other pages
See the [docs/en](docs/en/introduction.md) folder.
You have to do a couple things to get a Widget to work on a page.
## Versioning
This library follows [Semver](http://semver.org). According to Semver, you will be able to upgrade to any minor or patch version of this library without any breaking changes to the public API. Semver also requires that we clearly define the public API for this library.
## Reporting Issues
Please [create an issue](http://github.com/silverstripe/silverstripe-widgets/issues) for any bugs you've found, or features you're missing.
* Install the Widgets Module, see above.
* Add a WidgetArea field to your Page.
* Add a new tab to the CMS with a WidgetAreaEditor field for managing the widgets.
* Add a WidgetArea field to your Page.
* Add a new tab to the CMS with a WidgetAreaEditor field for managing the widgets.
e.g.
**mysite/code/Page.php**
class Page extends SiteTree {
...
private static $has_one = array(
"MyWidgetArea" => "WidgetArea",
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab("Root.Widgets", new WidgetAreaEditor("MyWidgetArea"));
return $fields;
}
}
```php
<?php
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.
@ -93,78 +114,97 @@ 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"
);
private static $defaults = array(
"NumberToShow" => 8
);
private static $title = "Photos";
private static $cmsTitle = "Flickr Photos";
private 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 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")
);
}
}
```php
<?php
namespace Yourname\MyWidget;
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;
class FlickrWidget extends Widget
{
private static $db = array(
'User' => 'Varchar',
'Photoset' => 'Varchar',
'Tags' => 'Varchar',
'NumberToShow' => 'Int'
);
private static $defaults = array(
'NumberToShow' => 8
);
private static $title = 'Photos';
private static $cmsTitle = 'Flickr Photos';
private static $description = 'Shows flickr photos.';
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');
$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.
@ -184,8 +224,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": [
@ -196,6 +236,11 @@ You need to finish off / change:
],
"extra" : {
"installer-name": "widgets_"
},
"autoload": {
"psr-4": {
"Yourname\\MyWidget\\": "src/"
}
}
}
```
@ -205,59 +250,64 @@ You need to finish off / change:
### 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.
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.
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.
@ -269,74 +319,100 @@ 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'
);
}
class MyWidget_Controller 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
}
}
```php
<?php
namespace Yourname\MyWidget;
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
## Contributing
### Translations
Translations of the natural language strings are managed through a
@ -347,4 +423,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](http://doc.silverstripe.org/framework/en/trunk/topics/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
Name: widgetscontentcontrollerurlhandler
---
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,6 @@
Director:
---
Name: widgetsroutes
---
SilverStripe\Control\Director:
rules:
'WidgetController//$Action/$ID/$OtherID': 'WidgetController'
'WidgetController//$Action/$ID/$OtherID': 'SilverStripe\Widgets\Model\WidgetController'

150
changelog.md Normal file
View File

@ -0,0 +1,150 @@
# Change Log
## [1.2.2](https://github.com/silverstripe/silverstripe-widgets/tree/1.2.2) (2016-02-04)
[Full Changelog](https://github.com/silverstripe/silverstripe-widgets/compare/1.2.1...1.2.2)
**Closed issues:**
- Broken Links in README [\#119](https://github.com/silverstripe/silverstripe-widgets/issues/119)
- Loop Children / with has\_one \(page\) not working [\#81](https://github.com/silverstripe/silverstripe-widgets/issues/81)
**Merged pull requests:**
- Fix readme.md links [\#120](https://github.com/silverstripe/silverstripe-widgets/pull/120) ([tractorcow](https://github.com/tractorcow))
- Updated license year [\#118](https://github.com/silverstripe/silverstripe-widgets/pull/118) ([helpfulrobot](https://github.com/helpfulrobot))
- ItemsToRender might return NULL // avoid warnings [\#117](https://github.com/silverstripe/silverstripe-widgets/pull/117) ([steiha](https://github.com/steiha))
- Added standard code of conduct [\#116](https://github.com/silverstripe/silverstripe-widgets/pull/116) ([helpfulrobot](https://github.com/helpfulrobot))
- Remove invalid 3.1 test fixture [\#115](https://github.com/silverstripe/silverstripe-widgets/pull/115) ([tractorcow](https://github.com/tractorcow))
- Added standard git attributes [\#114](https://github.com/silverstripe/silverstripe-widgets/pull/114) ([helpfulrobot](https://github.com/helpfulrobot))
- Added license to composer.json [\#113](https://github.com/silverstripe/silverstripe-widgets/pull/113) ([helpfulrobot](https://github.com/helpfulrobot))
- Added standard license [\#112](https://github.com/silverstripe/silverstripe-widgets/pull/112) ([helpfulrobot](https://github.com/helpfulrobot))
- Update changelog for 1.2.1 [\#111](https://github.com/silverstripe/silverstripe-widgets/pull/111) ([tractorcow](https://github.com/tractorcow))
- Added standard Travis config [\#110](https://github.com/silverstripe/silverstripe-widgets/pull/110) ([helpfulrobot](https://github.com/helpfulrobot))
- Added standard editor config [\#109](https://github.com/silverstripe/silverstripe-widgets/pull/109) ([helpfulrobot](https://github.com/helpfulrobot))
- Converted to PSR-2 [\#107](https://github.com/silverstripe/silverstripe-widgets/pull/107) ([helpfulrobot](https://github.com/helpfulrobot))
## [1.2.1](https://github.com/silverstripe/silverstripe-widgets/tree/1.2.1) (2015-11-18)
[Full Changelog](https://github.com/silverstripe/silverstripe-widgets/compare/1.1.2...1.2.1)
**Implemented enhancements:**
- Making widgets able to be used in a non page controller [\#105](https://github.com/silverstripe/silverstripe-widgets/pull/105) ([pitchandtone](https://github.com/pitchandtone))
**Merged pull requests:**
- Added standard Travis config [\#108](https://github.com/silverstripe/silverstripe-widgets/pull/108) ([helpfulrobot](https://github.com/helpfulrobot))
- Added standard Scrutinizer config [\#106](https://github.com/silverstripe/silverstripe-widgets/pull/106) ([helpfulrobot](https://github.com/helpfulrobot))
- OSS-905 Moved contributions of Widgets to contributing.md [\#103](https://github.com/silverstripe/silverstripe-widgets/pull/103) ([shoaibali](https://github.com/shoaibali))
- OSS-905 Moved docs to docs/en/ [\#102](https://github.com/silverstripe/silverstripe-widgets/pull/102) ([shoaibali](https://github.com/shoaibali))
- OSS-905 Added code of conduct [\#101](https://github.com/silverstripe/silverstripe-widgets/pull/101) ([shoaibali](https://github.com/shoaibali))
- OSS-905 Added changelog [\#100](https://github.com/silverstripe/silverstripe-widgets/pull/100) ([shoaibali](https://github.com/shoaibali))
- OSS-905 Moved contributing section to contributing.md [\#99](https://github.com/silverstripe/silverstripe-widgets/pull/99) ([shoaibali](https://github.com/shoaibali))
- OSS-905 Added gitattributes [\#98](https://github.com/silverstripe/silverstripe-widgets/pull/98) ([shoaibali](https://github.com/shoaibali))
- Added Scrutinizer support [\#96](https://github.com/silverstripe/silverstripe-widgets/pull/96) ([assertchris](https://github.com/assertchris))
- BUG Prevent non-widget tests from loading WidgetAreaEditorTest\_FakePage every query [\#95](https://github.com/silverstripe/silverstripe-widgets/pull/95) ([tractorcow](https://github.com/tractorcow))
- Tests for php 5.6 and framework 3.2 [\#94](https://github.com/silverstripe/silverstripe-widgets/pull/94) ([tractorcow](https://github.com/tractorcow))
- Support widget forms with InheritSidebar [\#93](https://github.com/silverstripe/silverstripe-widgets/pull/93) ([hailwood](https://github.com/hailwood))
## [1.1.2](https://github.com/silverstripe/silverstripe-widgets/tree/1.1.2) (2015-08-20)
[Full Changelog](https://github.com/silverstripe/silverstripe-widgets/compare/1.1.1...1.1.2)
## [1.1.1](https://github.com/silverstripe/silverstripe-widgets/tree/1.1.1) (2015-05-19)
[Full Changelog](https://github.com/silverstripe/silverstripe-widgets/compare/1.2.0...1.1.1)
**Merged pull requests:**
- Updated translations [\#90](https://github.com/silverstripe/silverstripe-widgets/pull/90) ([assertchris](https://github.com/assertchris))
- BUG Fix incorrect extension variable [\#89](https://github.com/silverstripe/silverstripe-widgets/pull/89) ([tractorcow](https://github.com/tractorcow))
## [1.2.0](https://github.com/silverstripe/silverstripe-widgets/tree/1.2.0) (2015-05-04)
[Full Changelog](https://github.com/silverstripe/silverstripe-widgets/compare/1.1.0...1.2.0)
**Merged pull requests:**
- Move 3.2+ compat version to 1.2 [\#87](https://github.com/silverstripe/silverstripe-widgets/pull/87) ([tractorcow](https://github.com/tractorcow))
## [1.1.0](https://github.com/silverstripe/silverstripe-widgets/tree/1.1.0) (2015-05-03)
[Full Changelog](https://github.com/silverstripe/silverstripe-widgets/compare/0.2.0...1.1.0)
**Closed issues:**
- Tests fail under silverstripe-framework 3 branch [\#86](https://github.com/silverstripe/silverstripe-widgets/issues/86)
- Widgets not clickable or draggable [\#82](https://github.com/silverstripe/silverstripe-widgets/issues/82)
- Please start using tags [\#79](https://github.com/silverstripe/silverstripe-widgets/issues/79)
**Merged pull requests:**
- BUGFIX: Fixed issue where Widget::CMSEditor\(\) can't rename the enabled checkbox [\#85](https://github.com/silverstripe/silverstripe-widgets/pull/85) ([UndefinedOffset](https://github.com/UndefinedOffset))
- API Enable Title to be CMS driven [\#84](https://github.com/silverstripe/silverstripe-widgets/pull/84) ([tractorcow](https://github.com/tractorcow))
- Remove reliance on $\_REQUEST global [\#83](https://github.com/silverstripe/silverstripe-widgets/pull/83) ([tractorcow](https://github.com/tractorcow))
- Adding .editorconfig [\#80](https://github.com/silverstripe/silverstripe-widgets/pull/80) ([dhensby](https://github.com/dhensby))
- BUG: Fixed issue where beforeSave\(\) was not being called when saving [\#77](https://github.com/silverstripe/silverstripe-widgets/pull/77) ([UndefinedOffset](https://github.com/UndefinedOffset))
## [0.2.0](https://github.com/silverstripe/silverstripe-widgets/tree/0.2.0) (2014-08-06)
**Implemented enhancements:**
- Coding conventions [\#18](https://github.com/silverstripe/silverstripe-widgets/issues/18)
- Adapt the CMS UI to fit the SS 3.0 UI [\#17](https://github.com/silverstripe/silverstripe-widgets/issues/17)
**Fixed bugs:**
- Widgets no longer work on SS3.1 [\#40](https://github.com/silverstripe/silverstripe-widgets/issues/40)
**Closed issues:**
- what about 3.0? [\#64](https://github.com/silverstripe/silverstripe-widgets/issues/64)
- Change $Content template variable? [\#60](https://github.com/silverstripe/silverstripe-widgets/issues/60)
- On `SiteTree` duplicate `WidgetArea`s are linked to both original and duplicate object [\#58](https://github.com/silverstripe/silverstripe-widgets/issues/58)
- 'Not Found' error on 3.1 means Widgets cannot be added [\#56](https://github.com/silverstripe/silverstripe-widgets/issues/56)
- BUG: Whitespace at the beginning of WidgetContentControllerExtension [\#53](https://github.com/silverstripe/silverstripe-widgets/issues/53)
- \[User Deprecated\] Object::get\_static is deprecated. Replaced by Config\#get. Called from Widget-\>CMSTitle [\#42](https://github.com/silverstripe/silverstripe-widgets/issues/42)
- New widget shown on top, then moved at bottom after page save [\#41](https://github.com/silverstripe/silverstripe-widgets/issues/41)
- widget causes page ajax to not terminate correct [\#35](https://github.com/silverstripe/silverstripe-widgets/issues/35)
- Widget wont render in Frontend [\#33](https://github.com/silverstripe/silverstripe-widgets/issues/33)
- Widget module not installing [\#32](https://github.com/silverstripe/silverstripe-widgets/issues/32)
- wrong filename for WigetContentControllerExtension.php [\#30](https://github.com/silverstripe/silverstripe-widgets/issues/30)
- DataObject error with Blog and Widgets modules [\#27](https://github.com/silverstripe/silverstripe-widgets/issues/27)
- Widgets area [\#23](https://github.com/silverstripe/silverstripe-widgets/issues/23)
- Translations [\#10](https://github.com/silverstripe/silverstripe-widgets/issues/10)
- widget management in the cms is broken [\#9](https://github.com/silverstripe/silverstripe-widgets/issues/9)
- Missing Resources [\#2](https://github.com/silverstripe/silverstripe-widgets/issues/2)
- ArchiveWidget is not translatable [\#1](https://github.com/silverstripe/silverstripe-widgets/issues/1)
**Merged pull requests:**
- Proposed fix for issue \#25 [\#73](https://github.com/silverstripe/silverstripe-widgets/pull/73) ([UndefinedOffset](https://github.com/UndefinedOffset))
- FIX Setting `$only\_available\_in` to `private` visibility [\#70](https://github.com/silverstripe/silverstripe-widgets/pull/70) ([dhensby](https://github.com/dhensby))
- Use Config to get Widget::$only\_available\_in [\#69](https://github.com/silverstripe/silverstripe-widgets/pull/69) ([ryanwachtl](https://github.com/ryanwachtl))
- NEW Adding support for Translatable [\#68](https://github.com/silverstripe/silverstripe-widgets/pull/68) ([tomspeak](https://github.com/tomspeak))
- Fixed locale for AJAX requests on addWidget. [\#67](https://github.com/silverstripe/silverstripe-widgets/pull/67) ([Sixfifty](https://github.com/Sixfifty))
- BUG Fixed issue with nested InheritSideBar not inheriting beyond the first level [\#66](https://github.com/silverstripe/silverstripe-widgets/pull/66) ([tractorcow](https://github.com/tractorcow))
- Change of yml name [\#65](https://github.com/silverstripe/silverstripe-widgets/pull/65) ([Leapfrognz](https://github.com/Leapfrognz))
- Added instructions for releasing a widget [\#63](https://github.com/silverstripe/silverstripe-widgets/pull/63) ([jedateach](https://github.com/jedateach))
- Added forTemplate function on Widget [\#62](https://github.com/silverstripe/silverstripe-widgets/pull/62) ([jedateach](https://github.com/jedateach))
- Added summary fields [\#61](https://github.com/silverstripe/silverstripe-widgets/pull/61) ([jedateach](https://github.com/jedateach))
- FIX \#58 on page duplicate [\#59](https://github.com/silverstripe/silverstripe-widgets/pull/59) ([MarkMhn](https://github.com/MarkMhn))
- Finnish language [\#57](https://github.com/silverstripe/silverstripe-widgets/pull/57) ([mediaclinic](https://github.com/mediaclinic))
- Update WidgetPageExtension.php [\#55](https://github.com/silverstripe/silverstripe-widgets/pull/55) ([MattyBalaam](https://github.com/MattyBalaam))
- Removed whitespace, fixes \#53 [\#54](https://github.com/silverstripe/silverstripe-widgets/pull/54) ([christopherdarling](https://github.com/christopherdarling))
- FIX: WidgetControllerTest failing due to 3.1 API. [\#52](https://github.com/silverstripe/silverstripe-widgets/pull/52) ([wilr](https://github.com/wilr))
- Updated Widget module to show hide Widgets in specified WidgetArea [\#51](https://github.com/silverstripe/silverstripe-widgets/pull/51) ([ghost](https://github.com/ghost))
- BUGFIX: solved bug adding a new widget to the top of the widget area, th... [\#46](https://github.com/silverstripe/silverstripe-widgets/pull/46) ([g4b0](https://github.com/g4b0))
- Bugfix: check array before shifting [\#45](https://github.com/silverstripe/silverstripe-widgets/pull/45) ([g4b0](https://github.com/g4b0))
- MINOR: Added Dutch language [\#38](https://github.com/silverstripe/silverstripe-widgets/pull/38) ([ARNHOE](https://github.com/ARNHOE))
- ENHANCEMENT added missing translations, I do not speak swedish and added... [\#34](https://github.com/silverstripe/silverstripe-widgets/pull/34) ([rlehmann](https://github.com/rlehmann))
- ENHANCEMENT: Adapt the Widgets Editor CMS UI to fit SS 3.0 [\#31](https://github.com/silverstripe/silverstripe-widgets/pull/31) ([ryanwachtl](https://github.com/ryanwachtl))
- german translation [\#28](https://github.com/silverstripe/silverstripe-widgets/pull/28) ([ivoba](https://github.com/ivoba))
- Coding conventions [\#19](https://github.com/silverstripe/silverstripe-widgets/pull/19) ([stojg](https://github.com/stojg))
- Bug : Removed errors from Composer.json [\#15](https://github.com/silverstripe/silverstripe-widgets/pull/15) ([vikas-srivastava](https://github.com/vikas-srivastava))
- ENHANCEMENT: Added Swedish translation file [\#14](https://github.com/silverstripe/silverstripe-widgets/pull/14) ([forsdahl](https://github.com/forsdahl))
- MINOR: Added a lang folder with default English translation and manifest... [\#13](https://github.com/silverstripe/silverstripe-widgets/pull/13) ([forsdahl](https://github.com/forsdahl))
- ENHANCEMENT: Made title, cmstitle and description on widgets translatable [\#12](https://github.com/silverstripe/silverstripe-widgets/pull/12) ([forsdahl](https://github.com/forsdahl))
- New : Added composer.json [\#11](https://github.com/silverstripe/silverstripe-widgets/pull/11) ([vikas-srivastava](https://github.com/vikas-srivastava))
- BUG: set current locale when fetching editable widget segment [\#8](https://github.com/silverstripe/silverstripe-widgets/pull/8) ([forsdahl](https://github.com/forsdahl))
- BUG: Enable chosen in WidgetAreaEditor fields. [\#7](https://github.com/silverstripe/silverstripe-widgets/pull/7) ([forsdahl](https://github.com/forsdahl))
- Issue \#4: Adds missing requirements for WidgetAreaEditor [\#6](https://github.com/silverstripe/silverstripe-widgets/pull/6) ([dancras](https://github.com/dancras))
- Missing Resources [\#3](https://github.com/silverstripe/silverstripe-widgets/pull/3) ([nedmas](https://github.com/nedmas))
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*

1
code-of-conduct.md Normal file
View File

@ -0,0 +1 @@
When having discussions about this module in issues or pull request please adhere to the [SilverStripe Community Code of Conduct](https://docs.silverstripe.org/en/contributing/code_of_conduct).

View File

@ -1,65 +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;
// find WidgetArea relations
$widgetAreaRelations = array();
$hasOnes = $this->owner->data()->has_one();
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 = $this->owner->data()->$widgetAreaRelation()->Widgets(
sprintf('"Widget"."ID" = %d', $SQL_id)
)->First();
}
if(!$widget) {
user_error('No widget found', E_USER_ERROR);
}
return $widget->getController();
}
}

View File

@ -1,120 +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);
if($page = Director::get_current_page()) {
return $page->Link($segment);
}
return Controller::curr()->Link($segment);
}
/**
* @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() {
$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,77 +0,0 @@
<?php
/**
* Adds a single {@link WidgetArea} called "SideBar" to {@link Page} classes.
* Adjust your templates to render the resulting
* {@link WidgetArea} as required, through the $SideBarView placeholder.
*
* This extension is just an example on how to use the widgets functionality,
* feel free to create your own relationships, naming conventions, etc.
* without using this class.
*/
class WidgetPageExtension extends DataExtension {
private static $db = array(
'InheritSideBar' => 'Boolean',
);
private static $defaults = array(
'InheritSideBar' => true
);
private static $has_one = array(
'SideBar' => 'WidgetArea'
);
public function updateCMSFields(FieldList $fields) {
$fields->addFieldToTab(
"Root.Widgets",
new CheckboxField("InheritSideBar", 'Inherit Sidebar From Parent')
);
$fields->addFieldToTab(
"Root.Widgets",
new WidgetAreaEditor("SideBar")
);
}
/**
* @return WidgetArea
*/
public function SideBarView() {
if(
$this->owner->InheritSideBar
&& ($parent = $this->owner->getParent())
&& $parent->hasMethod('SideBarView')
) {
return $parent->SideBarView();
} elseif($this->owner->SideBar()->exists()){
return $this->owner->SideBar();
}
}
public function onBeforeDuplicate($duplicatePage) {
if($this->owner->hasField('SideBarID')) {
$sideBar = $this->owner->getComponent('SideBar');
$duplicateWidgetArea = $sideBar->duplicate();
foreach($sideBar->Items() as $originalWidget) {
$widget = $originalWidget->duplicate(false);
$widget->ParentID = $duplicateWidgetArea->ID;
$widget->write();
}
$duplicatePage->SideBarID = $duplicateWidgetArea->ID;
}
return $duplicatePage;
}
/**
* Support Translatable so that we don't link WidgetAreas across translations
*/
public function onTranslatableCreate() {
//reset the sidebar ID
$this->owner->SideBarID = 0;
}
}

View File

@ -1,178 +0,0 @@
<?php
/**
* Special field type for selecting and configuring widgets on a page.
*
* @package widgets
*/
class WidgetAreaEditor extends FormField {
/**
* @param string $name
* @param array $widgetClasses
* @param int $maxWidgets
*/
public function __construct($name, $widgetClasses = array('Widget'), $maxWidgets = 0) {
$this->MaxWidgets = $maxWidgets;
$this->widgetClasses = $widgetClasses;
parent::__construct($name);
}
/**
* @param array $properties
*
* @return string - HTML
*/
public function FieldHolder($properties = array()) {
Requirements::css('widgets/css/WidgetAreaEditor.css');
Requirements::javascript('widgets/javascript/WidgetAreaEditor.js');
return $this->renderWith("WidgetAreaEditor");
}
/**
*
* @return ArrayList
*/
public function AvailableWidgets() {
$widgets= new ArrayList();
foreach($this->widgetClasses as $widgetClass) {
$classes = ClassInfo::subclassesFor($widgetClass);
if (isset($classes['Widget'])) {
unset($classes['Widget']);
}
else if (isset($classes[0]) && $classes[0] == 'Widget') {
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));
}
}else {
$widgets->push(singleton($class));
}
}
}
return $widgets;
}
/**
* @return HasManyList
*/
public 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;
}
/**
* @return string
*/
public function IdxField() {
return $this->id() . 'ID';
}
/**
*
* @return int
*/
public function Value() {
$relationName = $this->name;
return $this->form->getRecord()->getComponent($relationName)->ID;
}
/**
* @param DataObjectInterface $record
*/
public 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(!$this->getForm()) throw new Exception("no form");
$widgetData = $this->getForm()->getRequest()->requestVar('Widget');
if($widgetData && isset($widgetData[$this->getName()])) {
$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;
}
$widget = null;
if($newWidgetID) {
// \"ParentID\" = '0' is for the new page
$widget = Widget::get()
->filter('ParentID', array(0, $record->$name()->ID))
->byID($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'])
&& is_subclass_of($newWidgetData['Type'], 'Widget')
) {
$widget = Injector::inst()->create($newWidgetData['Type']);
$widget->ID = 0;
$widget->ParentID = $record->$name()->ID;
}
if($widget) {
if($widget->ParentID == 0) {
$widget->ParentID = $record->$name()->ID;
}
$widget->populateFromPostData($newWidgetData);
}
}
}
// remove the fields not saved
if($missingWidgets) {
foreach($missingWidgets as $removedWidget) {
if(isset($removedWidget) && is_numeric($removedWidget->ID)) {
$removedWidget->delete();
}
}
}
}
}

View File

@ -1,296 +0,0 @@
<?php
/**
* 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.
*
* @package widgets
*/
class Widget extends DataObject {
/**
* @var array
*/
private static $db = array(
"Title" => "Varchar(255)",
"Sort" => "Int",
"Enabled" => "Boolean",
);
/**
* @var array
*/
private static $defaults = array(
'Enabled' => true,
);
/**
* @var array
*/
private static $casting = array(
'CMSTitle' => 'Text',
'Description' => 'Text',
);
private static $only_available_in = array();
/**
* @var array
*/
private static $has_one = array(
"Parent" => "WidgetArea",
);
/**
* @var string
*/
private static $default_sort = "\"Sort\"";
/**
* @var string
*/
private static $title = "Widget Title";
/**
* @var string
*/
private static $cmsTitle = "Name of this widget";
/**
* @var string
*/
private static $description = "Description of what this widget does.";
/**
* @var array
*/
private static $summary_fields = array(
'CMSTitle' => 'Title'
);
/**
* @var WidgetController
*/
protected $controller;
public function populateDefaults() {
parent::populateDefaults();
$this->setField('Title', $this->getTitle());
}
/**
* Note: Overloaded in {@link WidgetController}.
*
* @return string HTML
*/
public function WidgetHolder() {
return $this->renderWith("WidgetHolder");
}
/**
* Default way to render widget in templates.
* @return string HTML
*/
public function forTemplate($holder = true){
if($holder){
return $this->WidgetHolder();
}
return $this->Content();
}
/**
* 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 WidgetController}.
*
* @return string HTML
*/
public function Content() {
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
*
* @return string
*/
public function getTitle() {
return $this->getField('Title')
?: _t($this->class.'.TITLE', $this->config()->title);
}
/**
* @return string
* @deprecated
*/
public function CMSTitle() {
return $this->getCMSTitle();
}
/**
* @return string
*/
public function getCMSTitle() {
return _t($this->class.'.CMSTITLE', $this->config()->cmsTitle);
}
/**
* @return string
* @deprecated
*/
public function Description() {
return $this->getDescription();
}
/**
* @return string
*/
public function getDescription() {
return _t($this->class.'.DESCRIPTION', $this->config()->description);
}
/**
* @return string - HTML
*/
public function DescriptionSegment() {
return $this->renderWith('WidgetDescription');
}
/**
* @see WidgetController::editablesegment()
*
* @return string - HTML
*/
public function EditableSegment() {
return $this->renderWith('WidgetEditor');
}
/**
* @return FieldList
*/
public function getCMSFields() {
$fields = new FieldList(
new TextField('Title', $this->fieldLabel('Title'), null, 255),
new CheckboxField('Enabled', $this->fieldLabel('Enabled'))
);
$this->extend('updateCMSFields', $fields);
return $fields;
}
/**
* @return FieldList
*/
public function CMSEditor() {
$fields = $this->getCMSFields();
$outputFields = new FieldList();
foreach($fields as $field) {
$name = $field->getName();
$value = $this->getField($name);
if ($value) {
$field->setValue($value);
}
$name = preg_replace("/([A-Za-z0-9\-_]+)/", "Widget[" . $this->ID . "][\\1]", $name);
$field->setName($name);
$outputFields->push($field);
}
return $outputFields;
}
/**
* @return string
*/
public function ClassName() {
return $this->class;
}
/**
* @return string
*/
public function Name() {
return "Widget[".$this->ID."]";
}
/**
* @throws Exception
*
* @return WidgetController
*/
public function getController() {
if($this->controller) {
return $this->controller;
}
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;
}
}
if(!class_exists($controllerClass)) {
throw new Exception("Could not find controller class for $this->classname");
}
$this->controller = Injector::inst()->create($controllerClass, $this);
return $this->controller;
}
/**
* @param array $data
*/
public function populateFromPostData($data) {
$fields = $this->getCMSFields();
foreach($data as $name => $value) {
if($name != "Type") {
if ($field = $fields->dataFieldByName($name)) {
$field->setValue($value);
$field->saveInto($this);
}
else {
$this->setField($name, $value);
}
}
}
//Look for checkbox fields not present in the data
foreach($fields as $field) {
if($field instanceof CheckboxField && !array_key_exists($field->getName(), $data)) {
$field->setValue(false);
$field->saveInto($this);
}
}
$this->write();
// The field must be written to ensure a unique ID.
$this->Name = $this->class.$this->ID;
$this->write();
}
}

View File

@ -1,82 +0,0 @@
<?php
/**
* Represents a set of widgets shown on a page.
*
* @package widgets
*/
class WidgetArea extends DataObject {
/**
* @var array
*/
private static $has_many = array(
"Widgets" => "Widget"
);
/**
*
* @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
* actions stored in {@link WidgetController}.
*
* @return SS_List - Collection of {@link WidgetController} instances.
*/
public function WidgetControllers() {
$controllers = new ArrayList();
foreach($this->ItemsToRender() as $widget) {
$controller = $widget->getController();
$controller->init();
$controllers->push($controller);
}
return $controllers;
}
/**
* @return HasManyList
*/
public function Items() {
return $this->getComponents('Widgets');
}
/**
* @return HasManyList
*/
public function ItemsToRender() {
return $this->getComponents('Widgets', "\"Widget\".\"Enabled\" = 1");
}
/**
* @return string - HTML
*/
public function forTemplate() {
return $this->renderWith($this->template);
}
/**
*
* @param string $template
*/
public function setTemplate($template) {
$this->template = $template;
}
/**
* Delete all connected Widgets when this WidgetArea gets deleted
*/
public function onBeforeDelete() {
parent::onBeforeDelete();
foreach($this->Widgets() as $widget) {
$widget->delete();
}
}
}

1
codecov.yml Normal file
View File

@ -0,0 +1 @@
comment: false

View File

@ -1,24 +1,41 @@
{
"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": "3.1.*",
"silverstripe/cms": "3.1.*"
},
"require-dev": {
"phpunit/PHPUnit": "~3.7@stable"
},
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
}
}
"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-vendormodule",
"keywords": [
"silverstripe",
"widgets",
"blog"
],
"authors": [
{
"name": "Ingo Schommer",
"email": "ingo@silverstripe.com"
}
],
"require": {
"silverstripe/vendor-plugin": "^1.0",
"silverstripe/framework": "^4.10",
"silverstripe/cms": "^4.0",
"silverstripe/versioned": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "^3.0"
},
"extra": {
"expose": [
"css",
"javascript"
]
},
"autoload": {
"psr-4": {
"SilverStripe\\Widgets\\": "src/",
"SilverStripe\\Widgets\\Tests\\": "tests/"
}
},
"license": "BSD-3-Clause",
"minimum-stability": "dev",
"prefer-stable": true
}

46
contributing.md Normal file
View File

@ -0,0 +1,46 @@
# Contributing
Contributions are welcome! Create an issue, explaining a bug or proposal. Submit pull requests if you feel brave. Speak to me on [Twitter](https://twitter.com/assertchris).
## Releasing a widget
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.
You need to finish off / change:
* name (eg: `yourorganisation/silverstripe-widget-carousel`)
* description
* keywords
* license
* author
* installer-name (eg: `widgets_carousel`)
```json
{
"name": "",
"description": "",
"type": "silverstripe-module",
"keywords" : ["widget"],
"require": {
"silverstripe/framework": "3.*",
"silverstripe/cms": "3.*"
},
"license": "BSD-2-Clause",
"authors": [
{
"name": "",
"email": ""
}
],
"extra" : {
"installer-name": "widgets_"
},
"autoload": {
"psr-4": {
"Yourname\\MyWidget\\": "src/"
}
}
}
```

View File

@ -1,10 +1,10 @@
@charset "UTF-8";
.WidgetAreaEditor p { margin-bottom: 15px; }
.Widget { padding: 0 7px 7px 7px; margin: 16px 0 16px 0; color: #1f1f1f; background-color: #b0bec7; background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2IwYmVjNyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzkyYTViMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=="); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b0bec7), color-stop(100%, #92a5b2)); background-image: -webkit-linear-gradient(#b0bec7, #92a5b2); background-image: -moz-linear-gradient(#b0bec7, #92a5b2); background-image: -o-linear-gradient(#b0bec7, #92a5b2); background-image: linear-gradient(#b0bec7, #92a5b2); border-top: 1px solid #c2cdd4; border-bottom: 1px solid #748d9d; -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; }
.Widget h3 { margin: 0; padding: 0; line-height: 40px; text-shadow: #bfcad2 1px 1px 0; -webkit-transition-property: background-image; -moz-transition-property: background-image; -o-transition-property: background-image; transition-property: background-image; -webkit-transition-duration: 0.2s; -moz-transition-duration: 0.2s; -o-transition-duration: 0.2s; transition-duration: 0.2s; -webkit-transition-timing-function: ease-out; -moz-transition-timing-function: ease-out; -o-transition-timing-function: ease-out; transition-timing-function: ease-out; }
.Widget .widgetDescription { background: #eceff1; padding: 5px 5px 1px 5px; margin: 0; -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; }
.Widget .widgetFields { background: #eceff1; padding: 5px; margin: 7px 0 0 0; -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; }
.Widget { padding: 0 1.25rem .75rem; margin: 16px 0 16px 0; color: #4F5861; background-color: #F1F3F6; border: 1px solid #ced5e1; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; }
.Widget h3 { color: #43536d; margin: 0; padding: 4px 0; line-height: 40px; -moz-transition-property: background-image; -o-transition-property: background-image; -webkit-transition-property: background-image; transition-property: background-image; -moz-transition-duration: 0.2s; -o-transition-duration: 0.2s; -webkit-transition-duration: 0.2s; transition-duration: 0.2s; -moz-transition-timing-function: ease-out; -o-transition-timing-function: ease-out; -webkit-transition-timing-function: ease-out; transition-timing-function: ease-out; }
.Widget .widgetDescription { padding: 0 0 .5rem; margin: 0; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; }
.Widget .widgetDescription p { margin: 0; }
.Widget .widgetFields { background: #F7F8FA; border-top: 1px solid #dbe0e9; border-bottom: 1px solid #dbe0e9; padding: .75rem 1.25rem; margin: .75rem -1.25rem; }
.availableWidgetsHolder { width: 38%; float: left; }

View File

@ -0,0 +1,5 @@
# Developer Tools
Get the dependencies by running `npm install`.
After making changes to SCSS files, run the build script `npm run build`. This will compile everything for you and output minified CSS files to the `css` directory.

209
docs/en/getting-started.md Normal file
View File

@ -0,0 +1,209 @@
# Getting Started
The easiest way to install is by using [Composer](https://getcomposer.org):
```sh
$ composer require silverstripe/widgets
```
You'll also need to run `dev/build`.
### Installation
Install the module through [composer](http://getcomposer.org):
```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
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`:
```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
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. Please ensure you add the correct namespaces for your module.
### Installing a widget
By following the "Packaging" rules below, widgets are easily installed. This example uses the Blog module which by default has widgets already enabled.
* Install the [blog module](http://www.silverstripe.org/blog-module/).
* Download the widget 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 click 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.
* Install the Widgets Module, see above.
* Add a WidgetArea field to your Page.
* Add a new tab to the CMS with a WidgetAreaEditor field for managing the widgets.
e.g.
**mysite/code/Page.php**
```php
<?php
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.
## 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 config variables:
* `title`: The title that will appear in the rendered widget (eg Photos). This can be customised by the CMS admin
* `cmsTitle`: a more descriptive title that will appear in the cms editor (eg Flickr Photos)
* `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
<?php
namespace Yourname\MyWidget;
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;
class FlickrWidget extends Widget
{
private static $db = array(
'User' => 'Varchar',
'Photoset' => 'Varchar',
'Tags' => 'Varchar',
'NumberToShow' => 'Int'
);
private static $defaults = array(
'NumberToShow' => 8
);
private static $title = 'Photos';
private static $cmsTitle = 'Flickr Photos';
private static $description = 'Shows flickr photos.';
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');
$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**
```
<% control Photos %>
<a href="$Link" rel="lightbox" title="$Title"><img src="$Image" alt="$Title" /></a>
<% end_control %>
```
## Limiting Allowed Widgets for a Pagetype
You can lock down a particular `WidgetAreaEditor` to only allow adding certain widgets by passing them as a second parameter.
**GreatPage.php**
```php
$fields->addFieldToTab(
'Root.Widgets',
new WidgetAreaEditor(
'PhenomenalWidgetArea',
[
'Fully\\Qualified\\ParticularlyEpicWidget',
'Yourname\\MyModule\\LessGreatWidget'
]
)
);
```

24
docs/en/introduction.md Normal file
View File

@ -0,0 +1,24 @@
# 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/)
[![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)
## Share Links
The generated share links have a public key and hash. There can be any number of share links per-page, but all share links are unique, and cannot be used to gain access to pages other than the one each link was created for.
## Sections
- [Getting Started](getting-started.md)
- [Developer Tools](developer-tools.md)
## Questions
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).

View File

@ -5,7 +5,7 @@
var parentName=$(this).attr('name');
this.rewriteWidgetAreaAttributes();
var availableWidgets=$('#availableWidgets-'+$(this).attr('name')).children().each(function() {
var availableWidgets=$('#availableWidgets-'+parentName).children().each(function() {
// Don't run on comments, whitespace, etc
if($(this)[0].nodeType==1) {
// Gotta change their ID's because otherwise we get clashes between two tabs
@ -20,26 +20,25 @@
$(this).find('.usedWidgets').sortable({
opacity: 0.6,
handle: '.handle',
update: function(e, ui) {parentRef.updateWidgets(e, ui)},
update: function (e, ui) {
parentRef.updateWidgets(e, ui)
},
placeholder: 'ui-state-highlight',
forcePlaceholderSize: true
});
// Figure out maxid, this is used when creating new widgets
$(this).data('maxid', 0);
var usedWidgets = $(this).find('.usedWidgets .Widget');
usedWidgets.each(function() {
var widget = $(this)[0];
if(widget.id) {
widgetid = widget.id.match(/Widget\[(.+?)\]\[([0-9]+)\]/i);
if(widgetid && parseInt(widgetid[2]) > parseInt(parentRef.data('maxid'))) {
parentRef.data('maxid', parseInt(widgetid[2]));
}
forcePlaceholderSize: true,
start: function (e, ui) {
htmleditors = $(ui.item).closest('.Widget').find('textarea.htmleditor');
$.each(htmleditors, function (k, i) {
tinyMCE.execCommand('mceRemoveControl', false, $(i).attr('id'));
})
},
stop: function (e, ui) {
htmleditors = $(ui.item).closest('.Widget').find('textarea.htmleditor');
$.each(htmleditors, function (k, i) {
tinyMCE.execCommand('mceAddControl', true, $(i).attr('id'));
})
}
});
// Ensure correct sort values are written when page is saved
// TODO Adjust to new event listeners
$('.cms-container').bind('submitform', function(e) {parentRef.beforeSave(e)});
@ -58,17 +57,13 @@
if (!widget.rewritten && (widget.id || widget.name)) {
if (widget.id && widget.id.indexOf('Widget[') === 0) {
var newValue = widget.id.replace(/Widget\[/, 'Widget['+name+'][');
//console.log('Renaming '+widget.tagName+' ID '+widget.id+' to '+newValue);
widget.id = newValue;
}
if (widget.name && widget.name.indexOf('Widget[') === 0) {
var newValue = widget.name.replace(/Widget\[/, 'Widget['+name+'][');
//console.log('Renaming '+widget.tagName+' Name '+widget.name+' to '+newValue);
widget.name=newValue;
}
widget.rewritten='yes';
}else {
//console.log('Skipping '+(widget.id ? widget.id : (widget.name ? widget.name : 'unknown '+widget.tagName)));
}
});
}
@ -130,13 +125,8 @@
},
insertWidgetEditor: function(response) {
var usedWidgets = $('#usedWidgets-'+$(this).attr('name')).children();
// Give the widget a unique id
var newID=parseInt($(this).data('maxid'))+1;
$(this).data('maxid', newID);
var widgetContent = response.replace(/Widget\[0\]/gi, "Widget[new-" + (newID) + "]");
var newID = $(response).find('.formid').val();
var widgetContent = response.replace(/Widget\[0\]/gi, "Widget[" + (newID) + "]");
$('#usedWidgets-'+$(this).attr('name')).append(widgetContent);
this.rewriteWidgetAreaAttributes();

View File

@ -1,17 +1,17 @@
ar:
WidgetAreaEditor_ss:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'التطبيقات المصغرة المتاحة'
AVAILWIDGETS: 'اضغط على عنوان تطبيق مصغر بالأسفل لاستخدامه بهذه الصفحة.'
NOAVAIL: 'لا توجد حاليا أية تطبيقات مصغرة متاحة.'
INUSE: 'التطبيقات المصغرة المستخدمة حاليا'
NOAVAIL: 'لا توجد حاليا أية تطبيقات مصغرة متاحة.'
TOSORT: 'لترتيب التطبيقات المصغرة المستخدمة حاليا فى هذه الصفحة, اسحبها لأعلى و لأسفل.'
WidgetEditor_ss:
DELETE: مسح
WidgetDescription_ss:
CLICKTOADDWIDGET: 'اضغط لإضافة هذا التطبيق المصغر'
WidgetArea:
SilverStripe\Widgets\Model\Widget:
PLURALNAME: 'تطبيقات مصغرة'
SINGULARNAME: 'تطبيق مصغر'
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'مساحات التطبيقات المصغرة'
SINGULARNAME: 'مساحة التطبيقات المصغرة'
Widget:
PLURALNAME: تطبيقات مصغرة
SINGULARNAME: تطبيق مصغر
WidgetDescription_ss:
CLICKTOADDWIDGET: 'اضغط لإضافة هذا التطبيق المصغر'
WidgetEditor_ss:
DELETE: مسح

View File

@ -1,17 +1,17 @@
de:
WidgetAreaEditor_ss:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Vorhandene Widgets'
AVAILWIDGETS: 'Klicke den Widget Titel, um es zu benutzen.'
NOAVAIL: 'Es sind derzeit keine Widgets verfügbar.'
INUSE: 'Benutzte Widgets'
NOAVAIL: 'Es sind derzeit keine Widgets verfügbar.'
TOSORT: 'Um die Widgets auf dieser Seite zu sortieren, ziehe sie nach oben oder unten.'
WidgetEditor_ss:
DELETE: Löschen
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Klicke um dieses Widget hinzuzufügen'
WidgetArea:
PLURALNAME: 'Widgetbereiche'
SINGULARNAME: 'Widgetbereich'
Widget:
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Widgets
SINGULARNAME: Widget
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: Widgetbereiche
SINGULARNAME: Widgetbereich
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Klicke um dieses Widget hinzuzufügen'
WidgetEditor_ss:
DELETE: Löschen

View File

@ -1,16 +1,24 @@
en:
Widget:
PLURALNAME: Widgets
SINGULARNAME: Widget
WidgetArea:
PLURALNAME: 'Widget Areas'
SINGULARNAME: 'Widget Area'
WidgetAreaEditor_ss:
SilverStripe\Widgets\Extensions\WidgetPageExtension:
INHERITSIDEBAR: 'Inherit Sidebar From Parent'
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Available Widgets'
AVAILWIDGETS: 'Click a widget title below to use it on this page.'
INUSE: 'Widgets currently used'
NOAVAIL: 'There are currently no widgets available.'
TOSORT: 'To sort currently used widgets on this page, drag them up and down.'
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Widgets
PLURALS:
one: 'A Widget'
other: '{count} Widgets'
SINGULARNAME: Widget
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'Widget Areas'
PLURALS:
one: 'A Widget Area'
other: '{count} Widget Areas'
SINGULARNAME: 'Widget Area'
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Click to add this widget'
WidgetEditor_ss:

View File

@ -1,17 +1,25 @@
eo:
WidgetAreaEditor_ss:
SilverStripe\Widgets\Extensions\WidgetPageExtension:
INHERITSIDEBAR: 'Heredi flankpanelon el patro'
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Disponeblaj fenestraĵoj'
AVAILWIDGETS: 'Alklaku sube titolon de fenestraĵo por uzi ĝin en ĉi tiu paĝo.'
NOAVAIL: 'Aktuale mankas disponeblaj fenestraĵoj.'
INUSE: 'Fenestraĵoj aktuale uzataj'
NOAVAIL: 'Aktuale mankas disponeblaj fenestraĵoj.'
TOSORT: 'Por ordigi aktuale uzatajn fenestraĵojn en ĉi tiu paĝo, ŝovu ilin supren kaj malsupren.'
WidgetEditor_ss:
DELETE: Forigi
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Fenestraĵoj
PLURALS:
one: 'Unu fenestraĵo'
other: '{count} fenestraĵoj'
SINGULARNAME: Fenestraĵo
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'Fenestraĵaj zonoj'
PLURALS:
one: 'Unu fenestraĵa zono'
other: '{count} fenestraĵaj zonoj'
SINGULARNAME: 'Fenestraĵa zono'
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Alklaku por aldoni la fenestraĵon'
WidgetArea:
PLURALNAME: 'Fenestraĵaj zonoj'
SINGULARNAME: 'Fenestraĵa zono'
Widget:
PLURALNAME: Fenestraĵoj
SINGULARNAME: Fenestraĵo
WidgetEditor_ss:
DELETE: Forigi

17
lang/fa_IR.yml Normal file
View File

@ -0,0 +1,17 @@
fa_IR:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'ویجت های موجود'
AVAILWIDGETS: 'بر روی عنوان یک ویجت در پایین کلیک کنید تا آن را در این صفحه بکار بگیرید.'
INUSE: 'ویجت هایی که در حال حاضر استفاده می شوند'
NOAVAIL: 'در حال حاضر هیچ ویجتی موجود نیست.'
TOSORT: 'برای مرتب سازی ویجت های این صفحه، آنها را بالا و پایین بکشید.'
SilverStripe\Widgets\Model\Widget:
PLURALNAME: 'ویجت ها'
SINGULARNAME: ویجت
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'محیط ویجت ها'
SINGULARNAME: 'محیط ویجت'
WidgetDescription_ss:
CLICKTOADDWIDGET: 'برای افزودن این ویجت کلیک کنید'
WidgetEditor_ss:
DELETE: حذف

View File

@ -1,17 +1,17 @@
fi:
WidgetAreaEditor_ss:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Käytettävissä olevat vimpaimet'
AVAILWIDGETS: 'Napsauta vimpaimen otsikkoa alla ottaaksesi se käyttöön tällä sivulla.'
NOAVAIL: 'Tällä hetkellä ei vimpaimia tarjolla.'
INUSE: 'Käytössä olevat vimpaimet'
NOAVAIL: 'Tällä hetkellä ei vimpaimia tarjolla.'
TOSORT: 'Järjestääksesi käytössä olevat vimpaimet, raahaa ja pudota ne paikoilleen.'
WidgetEditor_ss:
DELETE: Poista
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Napsauta lisätäksesi tämä vimpain'
WidgetArea:
PLURALNAME: 'Vimpain alueet'
SINGULARNAME: 'Vimpain alue'
Widget:
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Vimpaimet
SINGULARNAME: Vimpain
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'Vimpain alueet'
SINGULARNAME: 'Vimpain alue'
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Napsauta lisätäksesi tämä vimpain'
WidgetEditor_ss:
DELETE: Poista

17
lang/fi_FI.yml Normal file
View File

@ -0,0 +1,17 @@
fi_FI:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Käytettävissä olevat vimpaimet'
AVAILWIDGETS: 'Napsauta vimpaimen otsikkoa alla ottaaksesi se käyttöön tällä sivulla.'
INUSE: 'Käytössä olevat vimpaimet'
NOAVAIL: 'Tällä hetkellä ei vimpaimia tarjolla.'
TOSORT: 'Järjestääksesi käytössä olevat vimpaimet, raahaa ja pudota ne paikoilleen.'
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Vimpaimet
SINGULARNAME: Vimpain
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'Vimpain alueet'
SINGULARNAME: 'Vimpain alue'
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Napsauta lisätäksesi tämä vimpain'
WidgetEditor_ss:
DELETE: Poista

17
lang/hr.yml Normal file
View File

@ -0,0 +1,17 @@
hr:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Dostupni widgeti'
AVAILWIDGETS: 'Kliknite na naziv widgeta ispod za korištenje na ovoj stranici'
INUSE: 'Trenutno korišteni widgeti'
NOAVAIL: 'Trenutno nema dostupnih widgeta.'
TOSORT: 'Za sortiranje trenutno korištenih widgeta na ovoj stranici, rasporedite ih povlačeći gore i dolje.'
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Widgeti
SINGULARNAME: Widget
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'Zone widgeta'
SINGULARNAME: 'Zona widgeta'
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Klikni za dodavanje ovog widgeta'
WidgetEditor_ss:
DELETE: Obriši

5
lang/id_ID.yml Normal file
View File

@ -0,0 +1,5 @@
id_ID:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Widget Tersedia'
WidgetEditor_ss:
DELETE: Hapus

17
lang/it.yml Normal file
View File

@ -0,0 +1,17 @@
it:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Widget Disponibili'
AVAILWIDGETS: 'Clickare sul titolo di un widget per usarlo in questa pagina'
INUSE: 'Widget attualmente in uso'
NOAVAIL: 'Attualmente non ci sono widget disponibili'
TOSORT: 'Per riordinare i widget usati in questa pagina, trascinarli in alto o in basso.'
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Widget
SINGULARNAME: Widget
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'Aree Widget'
SINGULARNAME: 'Area Widget'
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Clickare per aggiungere questo widget'
WidgetEditor_ss:
DELETE: Elimina

View File

@ -1,17 +1,17 @@
it_IT:
WidgetAreaEditor_ss:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Widgets disponibili'
AVAILWIDGETS: 'Clicca sul titolo di un widget qui sotto per usarlo in questa pagina'
NOAVAIL: 'Non ci sono widgets attualmente disponibili.'
INUSE: 'Widgets attualmente utilizzati'
NOAVAIL: 'Non ci sono widgets attualmente disponibili.'
TOSORT: 'Per ordinare i widgets utilizzati in questa pagina, trascinali in alto o in basso.'
WidgetEditor_ss:
DELETE: Cancella
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Clicca per aggiungere questo widget'
WidgetArea:
PLURALNAME: 'Aree Widget'
SINGULARNAME: 'Area Widget'
Widget:
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Widgets
SINGULARNAME: Widget
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'Aree Widget'
SINGULARNAME: 'Area Widget'
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Clicca per aggiungere questo widget'
WidgetEditor_ss:
DELETE: Cancella

View File

@ -1,11 +1,17 @@
nl:
WidgetAreaEditor_ss:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Beschikbare Widgets'
AVAILWIDGETS: 'Klik op een widget titel hieronder, om de widget te gebruiken op deze pagina.'
NOAVAIL: 'Er zijn geen widgets beschikbaar.'
INUSE: 'Huidige gebruikte Widgets'
NOAVAIL: 'Er zijn geen widgets beschikbaar.'
TOSORT: 'Om uw widgets te sorteren op deze pagina, rangschik met drag & drop.'
WidgetEditor_ss:
DELETE: Verwijderen
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Widgets
SINGULARNAME: Widget
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'Widget plaatsen'
SINGULARNAME: 'Widget plaats'
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Klik om deze widget toe te voegen'
WidgetEditor_ss:
DELETE: Verwijderen

View File

@ -1,11 +1,11 @@
pl_PL:
WidgetAreaEditor_ss:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Dostępne widżety'
AVAILWIDGETS: 'Kliknij w tytuł widżetu, aby używać go na tej stronie.'
NOAVAIL: 'Nie ma żadnych dostępnych widżetów'
INUSE: 'Obecnie stosowane widżety'
NOAVAIL: 'Nie ma żadnych dostępnych widżetów'
TOSORT: 'Aby posortować używane widżety na tej stronie, przeciągnij je w górę i w dół.'
WidgetEditor_ss:
DELETE: Usuń
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Kliknij aby dodać ten widżet'
WidgetEditor_ss:
DELETE: Usuń

View File

@ -1,11 +1,11 @@
ro_RO:
WidgetAreaEditor_ss:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Widget-uri disponibile'
AVAILWIDGETS: 'Faceți clic pe un titlu widget de mai jos pentru a-l folosi pe aceasta pagina.'
NOAVAIL: 'Momentan nu exista widget-uri disponibile.'
INUSE: 'Widget-uri utilizate în prezent'
NOAVAIL: 'Momentan nu exista widget-uri disponibile.'
TOSORT: 'Pentru a sorta widget-uri utilizate în prezent pe această pagină, trageți-le în sus și în jos.'
WidgetEditor_ss:
DELETE: Ştergeţi
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Click pentru a adăuga acest widget'
WidgetEditor_ss:
DELETE: Ştergeţi

View File

@ -1,17 +1,17 @@
ru:
WidgetAreaEditor_ss:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Доступные виджеты'
AVAILWIDGETS: 'Щелкните заголовок виджета ниже, чтобы использовать его на этой странице.'
NOAVAIL: 'В данный момент доступных виджетов нет.'
INUSE: 'Используемые виджеты'
NOAVAIL: 'В данный момент доступных виджетов нет.'
TOSORT: 'Чтобы расположить используемые на странице виджеты в нужном порядке, перетащите их вверх или вниз.'
WidgetEditor_ss:
DELETE: Удалить
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Щелкните, чтобы добавить этот виджет'
WidgetArea:
PLURALNAME: 'Области виджетов'
SINGULARNAME: 'Область виджета'
Widget:
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Виджеты
SINGULARNAME: Виджет
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'Области виджетов'
SINGULARNAME: 'Область виджета'
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Щелкните, чтобы добавить этот виджет'
WidgetEditor_ss:
DELETE: Удалить

17
lang/sk.yml Normal file
View File

@ -0,0 +1,17 @@
sk:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Dostupné widgety'
AVAILWIDGETS: 'Kliknite na názov widgetu pre jeho použitie na tejto stránke.'
INUSE: 'Momentálne používané widgety.'
NOAVAIL: 'Momentálne nie sú dostupné žiadne widgety.'
TOSORT: 'Pre zotriedenie momentálne používaných widgetov na tejto stránke, potiahnite ich hore alebo dole.'
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Widgety
SINGULARNAME: Widget
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'Oblasti widgetu'
SINGULARNAME: 'Oblasť widgetu'
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Kliknite pre pridanie tohto widgetu'
WidgetEditor_ss:
DELETE: Vymazať

17
lang/sl.yml Normal file
View File

@ -0,0 +1,17 @@
sl:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Vtičniki, ki so na voljo'
AVAILWIDGETS: 'Spodaj izberite vtičnik, ki ga želite uporabiti na strani.'
INUSE: 'Vtičniki v uporabi'
NOAVAIL: 'Na voljo ni nobenega vtičnika.'
TOSORT: 'Za razvrstitev vtičnikov na strani jih povlecite na ustrezno mesto. '
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Vtičniki
SINGULARNAME: Vtičnik
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 'Prostori za vtičnike'
SINGULARNAME: 'Prostor za vtičnike'
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Kliknite na ta vtičnik, če ga želite dodati'
WidgetEditor_ss:
DELETE: Odstrani

View File

@ -1,17 +1,17 @@
sv:
WidgetAreaEditor_ss:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 'Tillgängliga widgets'
AVAILWIDGETS: 'Klicka på en widget nedan för att använda den på sidan.'
NOAVAIL: 'Inga widgets tillängliga.'
INUSE: 'Använda widgets'
NOAVAIL: 'Inga widgets tillängliga.'
TOSORT: 'För att sortera widgetar på denna sida, dra dem uppåt eller nedåt.'
WidgetEditor_ss:
DELETE: Radera
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Klicka för att lägga till denna widget'
WidgetArea:
PLURALNAME: 'Widgetområden'
SINGULARNAME: 'Widgetområde'
Widget:
SilverStripe\Widgets\Model\Widget:
PLURALNAME: Widgets
SINGULARNAME: Widget
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: Widgetområden
SINGULARNAME: Widgetområde
WidgetDescription_ss:
CLICKTOADDWIDGET: 'Klicka för att lägga till denna widget'
WidgetEditor_ss:
DELETE: Radera

View File

@ -1,17 +1,17 @@
zh:
WidgetAreaEditor_ss:
AVAILABLE: '可用的小工具'
AVAILWIDGETS: '点击下方的小工具名称即可在此页上使用。'
NOAVAIL: '目前没有可用小工具。'
INUSE: '当前使用的小工具'
TOSORT: '要对本页当前使用的小工具进行排序,请像上下拖拽。'
WidgetEditor_ss:
DELETE: 删除
WidgetDescription_ss:
CLICKTOADDWIDGET: '点击添加该小工具'
WidgetArea:
PLURALNAME: '小工具区域'
SINGULARNAME: '小工具区域'
Widget:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 可用的小工具
AVAILWIDGETS: 点击下方的小工具名称即可在此页上使用。
INUSE: 当前使用的小工具
NOAVAIL: 目前没有可用小工具。
TOSORT: 要对本页当前使用的小工具进行排序,请像上下拖拽。
SilverStripe\Widgets\Model\Widget:
PLURALNAME: 小工具
SINGULARNAME: 小工具
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 小工具区域
SINGULARNAME: 小工具区域
WidgetDescription_ss:
CLICKTOADDWIDGET: 点击添加该小工具
WidgetEditor_ss:
DELETE: 删除

17
lang/zh_CN.yml Normal file
View File

@ -0,0 +1,17 @@
zh_CN:
SilverStripe\Widgets\Forms\WidgetAreaEditor_ss:
AVAILABLE: 有效插件
AVAILWIDGETS: 点击一个插件标题应用在当前页面
INUSE: 当前已使用插件
NOAVAIL: 当前无有效插件
TOSORT: 请上下拖动当前页面的对应插件进行排序
SilverStripe\Widgets\Model\Widget:
PLURALNAME: 插件
SINGULARNAME: 插件
SilverStripe\Widgets\Model\WidgetArea:
PLURALNAME: 插件区域
SINGULARNAME: 插件区域
WidgetDescription_ss:
CLICKTOADDWIDGET: 点击增加该插件
WidgetEditor_ss:
DELETE: 删除

12
license.md Normal file
View File

@ -0,0 +1,12 @@
Copyright (c) 2017, SilverStripe Limited
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. 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.
3. Neither the name of the copyright holder 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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.

12
phpcs.xml.dist Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="SilverStripe">
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
<file>src</file>
<file>tests</file>
<rule ref="PSR2">
<!-- Current exclusions -->
<exclude name="PSR1.Methods.CamelCapsMethodName"/>
</rule>
</ruleset>

16
phpunit.xml.dist Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/silverstripe/cms/tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="Default">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src/</directory>
<exclude>
<directory suffix=".php">tests/</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -3,7 +3,7 @@
// root widget area editor container
.WidgetAreaEditor {
h2 {
}
p {
margin-bottom: 15px;
@ -12,40 +12,36 @@
// base styles for a widget
.Widget {
padding: 0 7px 7px 7px;
padding: 0 1.25rem .75rem;
margin: 16px 0 16px 0;
color: #1f1f1f;
background-color: #b0bec7;
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2IwYmVjNyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzkyYTViMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
background-size: 100%;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b0bec7), color-stop(100%, #92a5b2));
background-image: -webkit-linear-gradient(#b0bec7, #92a5b2);
background-image: -moz-linear-gradient(#b0bec7, #92a5b2);
background-image: -o-linear-gradient(#b0bec7, #92a5b2);
background-image: linear-gradient(#b0bec7, #92a5b2);
border-top: 1px solid #c2cdd4;
border-bottom: 1px solid #748d9d;
color: #4F5861;
background-color: #F1F3F6;
border: 1px solid #ced5e1;
@include border-radius(3px);
h3 {
color: #43536d;
margin: 0;
padding: 0;
padding: 4px 0;
line-height: 40px;
text-shadow: #bfcad2 1px 1px 0;
@include transition-property(background-image);
@include transition-duration(0.2s);
@include transition-timing-function(ease-out);
}
.widgetDescription {
background: #eceff1;
padding: 5px 5px 1px 5px;
padding: 0 0 .5rem;
margin: 0;
@include border-radius(3px);
p {
margin: 0;
}
}
.widgetFields {
background: #eceff1;
padding: 5px;
margin: 7px 0 0 0;
@include border-radius(3px);
background: #F7F8FA;
border-top: 1px solid #dbe0e9;
border-bottom: 1px solid #dbe0e9;
padding: .75rem 1.25rem;
margin: .75rem -1.25rem;
}
}
@ -89,7 +85,7 @@
}
.noWidgets {
}
// one column layout when space is limited
@ -103,4 +99,4 @@
width: 100%;
float: none;
}
}
}

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,86 @@
<?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
* {@link WidgetArea} as required, through the $SideBarView placeholder.
*
* This extension is just an example on how to use the widgets functionality,
* feel free to create your own relationships, naming conventions, etc.
* without using this class.
*/
class WidgetPageExtension extends DataExtension
{
private static $db = [
'InheritSideBar' => 'Boolean',
];
private static $defaults = [
'InheritSideBar' => true
];
private static $has_one = [
'SideBar' => WidgetArea::class,
];
private static $owns = [
'SideBar',
];
private static $cascade_deletes = [
'SideBar',
];
public function updateCMSFields(FieldList $fields)
{
$fields->addFieldToTab(
"Root.Widgets",
new CheckboxField("InheritSideBar", _t(__CLASS__ . '.INHERITSIDEBAR', 'Inherit Sidebar From Parent'))
);
$fields->addFieldToTab(
"Root.Widgets",
new WidgetAreaEditor("SideBar")
);
}
/**
* @return WidgetArea
*/
public function SideBarView()
{
if ($this->owner->InheritSideBar
&& ($parent = $this->owner->getParent())
&& $parent->hasMethod('SideBarView')
) {
return $parent->SideBarView();
} elseif ($this->owner->SideBar()->exists()) {
return $this->owner->SideBar();
}
}
public function onBeforeDuplicate($duplicatePage)
{
if ($this->owner->hasField('SideBarID')) {
$sideBar = $this->owner->getComponent('SideBar');
$duplicateWidgetArea = $sideBar->duplicate();
foreach ($sideBar->Items() as $originalWidget) {
$widget = $originalWidget->duplicate(false);
$widget->ParentID = $duplicateWidgetArea->ID;
$widget->write();
}
$duplicatePage->SideBarID = $duplicateWidgetArea->ID;
}
return $duplicatePage;
}
}

View File

@ -0,0 +1,202 @@
<?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\View\Requirements;
use SilverStripe\Widgets\Model\Widget;
/**
* Special field type for selecting and configuring widgets on a page.
*
* @package widgets
*/
class WidgetAreaEditor extends FormField
{
/**
* @param string $name
* @param array $widgetClasses
* @param int $maxWidgets
*/
public function __construct($name, $widgetClasses = array(Widget::class), $maxWidgets = 0)
{
$this->MaxWidgets = $maxWidgets;
$this->widgetClasses = $widgetClasses;
parent::__construct($name);
}
/**
* @param array $properties
*
* @return string - HTML
*/
public function FieldHolder($properties = array())
{
Requirements::css('silverstripe/widgets:css/WidgetAreaEditor.css');
Requirements::javascript('silverstripe/widgets:javascript/WidgetAreaEditor.js');
return $this->renderWith(WidgetAreaEditor::class);
}
/**
*
* @return ArrayList
*/
public function AvailableWidgets()
{
$widgets= new ArrayList();
foreach ($this->widgetClasses as $widgetClass) {
$classes = ClassInfo::subclassesFor($widgetClass);
if (isset($classes[strtolower(Widget::class)])) {
unset($classes[strtolower(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));
}
} else {
$widgets->push(singleton($class));
}
}
}
return $widgets;
}
/**
* @return HasManyList
*/
public function UsedWidgets()
{
// Call class_exists() to load Widget.php earlier and avoid a segfault
class_exists(Widget::class);
$relationName = $this->name;
$widgets = $this->form->getRecord()->getComponent($relationName)->Items();
return $widgets;
}
/**
* @return string
*/
public function IdxField()
{
return $this->id() . 'ID';
}
/**
*
* @return int
*/
public function Value()
{
$relationName = $this->name;
return $this->form->getRecord()->getComponent($relationName)->ID;
}
/**
* @param DataObjectInterface $record
* @throws Exception if no form could be retrieved
*/
public 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 (!$this->getForm()) {
throw new Exception("no form");
}
$widgetData = $this->getForm()->getController()->getRequest()->requestVar('Widget');
if ($widgetData && isset($widgetData[$this->getName()])) {
$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;
}
$widget = null;
if ($newWidgetID) {
// \"ParentID\" = '0' is for the new page
$widget = Widget::get()
->filter('ParentID', array(0, $record->$name()->ID))
->byID($newWidgetID);
// check if we are updating an existing widget
if ($widget && isset($missingWidgets[$widget->ID])) {
unset($missingWidgets[$widget->ID]);
}
}
// unsantise the class name
if (empty($newWidgetData['Type'])) {
$newWidgetData['Type'] = '';
}
$newWidgetData['Type'] = str_replace('_', '\\', $newWidgetData['Type'] ?? '');
// create a new object
if (!$widget
&& !empty($newWidgetData['Type'])
&& class_exists($newWidgetData['Type'] ?? '')
&& is_subclass_of($newWidgetData['Type'], Widget::class)
) {
$widget = Injector::inst()->create($newWidgetData['Type']);
$widget->ID = 0;
$widget->ParentID = $record->$name()->ID;
}
if ($widget) {
if ($widget->ParentID == 0) {
$widget->ParentID = $record->$name()->ID;
}
$widget->populateFromPostData($newWidgetData);
}
}
}
// remove the fields not saved
if ($missingWidgets) {
foreach ($missingWidgets as $removedWidget) {
if (isset($removedWidget) && is_numeric($removedWidget->ID)) {
$removedWidget->delete();
}
}
}
}
}

283
src/Model/Widget.php Normal file
View File

@ -0,0 +1,283 @@
<?php
namespace SilverStripe\Widgets\Model;
use Exception;
use SilverStripe\Control\HTTPRequest;
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\ORM\FieldType\DBHTMLText;
use SilverStripe\Versioned\Versioned;
use SilverStripe\View\SSViewer;
/**
* 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 WidgetController} for more information.
*
* @package widgets
*/
class Widget extends DataObject
{
private static $db = [
"Title" => "Varchar(255)",
"Sort" => "Int",
"Enabled" => "Boolean",
];
private static $defaults = [
'Enabled' => true,
];
private static $casting = [
'CMSTitle' => 'Text',
'Description' => 'Text',
];
private static $only_available_in = [];
private static $has_one = [
"Parent" => WidgetArea::class,
];
private static $default_sort = "\"Sort\"";
/**
* @var string
*/
private static $cmsTitle = "Name of this widget";
/**
* @var string
*/
private static $description = "Description of what this widget does.";
private static $summary_fields = [
'CMSTitle' => 'Title'
];
private static $table_name = 'Widget';
private static $extensions = [
Versioned::class,
];
/**
* @var WidgetController
*/
protected $controller;
public function populateDefaults()
{
parent::populateDefaults();
$this->setField('Title', $this->getTitle());
}
/**
* Note: Overloaded in {@link WidgetController}.
*
* @return string HTML
*/
public function WidgetHolder()
{
return $this->renderWith("WidgetHolder");
}
/**
* Default way to render widget in templates.
* @return string HTML
*/
public function forTemplate($holder = true)
{
if ($holder) {
return $this->WidgetHolder();
}
return $this->Content();
}
/**
* 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 WidgetController}.
*
* @return string HTML
*/
public function Content()
{
return $this->renderWith(SSViewer::get_templates_by_class(static::class));
}
/**
* @return string
*/
public function getCMSTitle()
{
return _t(__CLASS__ . '.CMSTITLE', $this->config()->get('cmsTitle'));
}
/**
* @return string
*/
public function getDescription()
{
return _t(__CLASS__ . '.DESCRIPTION', $this->config()->get('description'));
}
/**
* @return string - HTML
*/
public function DescriptionSegment()
{
return $this->renderWith('WidgetDescription');
}
/**
* @see WidgetController::editablesegment()
*
* @return string - HTML
*/
public function EditableSegment()
{
return $this->renderWith('WidgetEditor');
}
/**
* @return FieldList
*/
public function getCMSFields()
{
$fields = new FieldList(
new TextField('Title', $this->fieldLabel('Title'), null, 255),
new CheckboxField('Enabled', $this->fieldLabel('Enabled'))
);
$this->extend('updateCMSFields', $fields);
return $fields;
}
/**
* @return FieldList
*/
public function CMSEditor()
{
$fields = $this->getCMSFields();
$outputFields = new FieldList();
$this->FormID = $this->ID ?: uniqid();
$outputFields->push(
HiddenField::create(
'Widget[' . $this->FormID . '][FormID]',
'FormID',
$this->FormID
)->addExtraClass('formid')
);
foreach ($fields as $field) {
$name = $field->getName();
$value = $this->getField($name);
if ($value) {
$field->setValue($value);
}
$namefiltered = preg_replace("/([A-Za-z0-9\-_]+)/", "Widget[" . $this->FormID . "][\\1]", $name ?? '');
$field->setName($namefiltered);
$outputFields->push($field);
}
return $outputFields;
}
/**
* 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 str_replace('\\', '_', get_class($this));
}
/**
* @return string
*/
public function Name()
{
return "Widget[" . $this->ID . "]";
}
/**
* @throws Exception If the widget controller's class name couldn't be found
*
* @return WidgetController
*/
public function getController()
{
if ($this->controller) {
return $this->controller;
}
foreach (array_reverse(ClassInfo::ancestry(get_class($this)) ?? []) as $widgetClass) {
$controllerClass = "{$widgetClass}Controller";
if (class_exists($controllerClass ?? '')) {
break;
}
}
if (!class_exists($controllerClass ?? '')) {
throw new Exception('Could not find controller class for ' . static::class);
}
$this->controller = Injector::inst()->create($controllerClass, $this);
if (Injector::inst()->has(HTTPRequest::class)) {
$this->controller->setRequest(Injector::inst()->get(HTTPRequest::class));
}
return $this->controller;
}
/**
* @param array $data
*/
public function populateFromPostData($data)
{
$fields = $this->getCMSFields();
foreach ($data as $name => $value) {
if ($name != "Type") {
if ($field = $fields->dataFieldByName($name)) {
$field->setValue($value);
$field->saveInto($this);
} else {
$this->setField($name, $value);
}
}
}
//Look for checkbox fields not present in the data
foreach ($fields as $field) {
if ($field instanceof CheckboxField && !array_key_exists($field->getName(), $data ?? [])) {
$field->setValue(false);
$field->saveInto($this);
}
}
$this->write();
// The field must be written to ensure a unique ID.
$this->Name = get_class($this) . $this->ID;
$this->write();
}
}

94
src/Model/WidgetArea.php Normal file
View File

@ -0,0 +1,94 @@
<?php
namespace SilverStripe\Widgets\Model;
use SilverStripe\Control\Controller;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\HasManyList;
use SilverStripe\ORM\SS_List;
use SilverStripe\Versioned\Versioned;
/**
* Represents a set of widgets shown on a page.
*/
class WidgetArea extends DataObject
{
private static $has_many = [
"Widgets" => Widget::class
];
private static $owns = [
'Widgets',
];
private static $cascade_deletes = [
'Widgets',
];
private static $extensions = [
Versioned::class,
];
private static $table_name = 'WidgetArea';
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 WidgetController}.
*
* @return SS_List - Collection of {@link WidgetController} instances.
*/
public function WidgetControllers()
{
$controllers = new ArrayList();
$items = $this->ItemsToRender();
if (!is_null($items)) {
foreach ($items as $widget) {
/** @var Widget $widget */
/** @var Controller $controller */
$controller = $widget->getController();
$controller->doInit();
$controllers->push($controller);
}
}
return $controllers;
}
/**
* @return HasManyList
*/
public function Items()
{
return $this->Widgets();
}
/**
* @return HasManyList
*/
public function ItemsToRender()
{
return $this->Items()->filter('Enabled', 1);
}
/**
* @return string - HTML
*/
public function forTemplate()
{
return $this->renderWith($this->template);
}
/**
*
* @param string $template
*/
public function setTemplate($template)
{
$this->template = $template;
}
}

View File

@ -0,0 +1,148 @@
<?php
namespace SilverStripe\Widgets\Model;
use SilverStripe\Admin\LeftAndMain;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Core\ClassInfo;
/**
* 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(get_class($this->widget)) ?? []));
}
/**
* 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->setRequest($this->getRequest());
$leftandmain->doInit();
// Decode if fully qualified - @see Widget::ClassName
$className = str_replace('_', '\\', $this->urlParams['ID'] ?? '');
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

@ -0,0 +1,32 @@
<div class="WidgetAreaEditor field" 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 SilverStripe\\Widgets\\Forms\\WidgetAreaEditor_ss.AVAILABLE 'Available Widgets' %></h2>
<p class="message"><%t SilverStripe\\Widgets\\Forms\\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 %>
$DescriptionSegment
<% end_loop %>
<% else %>
<div class="NoWidgets" id="NoWidgets-$Name">
<p><%t SilverStripe\\Widgets\\Forms\\WidgetAreaEditor_ss.NOAVAIL 'There are currently no widgets available.' %></p>
</div>
<% end_if %>
</div>
</div>
<div class="usedWidgetsHolder">
<h2><%t SilverStripe\\Widgets\\Forms\\WidgetAreaEditor_ss.INUSE 'Widgets currently used' %></h2>
<p class="message"><%t SilverStripe\\Widgets\\Forms\\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 %>
$EditableSegment
<% end_loop %>
<% else %>
<div class="NoWidgets" id="NoWidgets-$Name"></div>
<% end_if %>
</div>
</div>
</div>

View File

@ -0,0 +1,3 @@
<% loop $WidgetControllers %>
$WidgetHolder
<% end_loop %>

View File

@ -1,3 +0,0 @@
<% loop WidgetControllers %>
$WidgetHolder
<% end_loop %>

View File

@ -1,32 +0,0 @@
<div class="WidgetAreaEditor field" 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('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 %>
$DescriptionSegment
<% end_loop %>
<% else %>
<div class="NoWidgets" id="NoWidgets-$Name">
<p><% _t('WidgetAreaEditor_ss.NOAVAIL', 'There are currently no widgets available.') %></p>
</div>
<% end_if %>
</div>
</div>
<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 %>
$EditableSegment
<% end_loop %>
<% else %>
<div class="NoWidgets" id="NoWidgets-$Name"></div>
<% end_if %>
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
<div class="Widget" id="$ClassName">
<h3 title="<% _t('WidgetDescription_ss.CLICKTOADDWIDGET', 'Click to add this widget') %>">$CMSTitle</h3>
<h3 title="<%t WidgetDescription_ss.CLICKTOADDWIDGET 'Click to add this widget' %>">$CMSTitle</h3>
<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,4 @@
<div class="WidgetHolder $ClassName<% if FirstLast %> $FirstLast<% end_if %>">
<% if Title %><h3>$Title</h3><% end_if %>
<div class="WidgetHolder $ClassName<% if $FirstLast %> $FirstLast<% end_if %>">
<% if $Title %><h3>$Title</h3><% end_if %>
$Content
</div>

View File

@ -1,442 +1,503 @@
<?php
namespace SilverStripe\Widgets\Tests;
use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Widgets\Extensions\WidgetPageExtension;
use SilverStripe\Widgets\Forms\WidgetAreaEditor;
use SilverStripe\Widgets\Tests\WidgetAreaEditorTest\FakePage;
use SilverStripe\Widgets\Tests\WidgetAreaEditorTest\TestWidget;
/**
* @package cms
* @subpackage tests
*/
class WidgetAreaEditorTest extends SapphireTest {
/**
* This is the widget you want to use for your unit tests.
*/
protected $widgetToTest = 'WidgetAreaEditorTest_TestWidget';
class WidgetAreaEditorTest extends SapphireTest
{
/**
* This is the widget you want to use for your unit tests.
*/
protected $widgetToTest = TestWidget::class;
protected $extraDataObjects = array(
'WidgetAreaEditorTest_FakePage',
'WidgetAreaEditorTest_TestWidget',
);
protected $usesDatabase = true;
function testFillingOneArea() {
$data = array(
'Widget' => array(
'BottomBar' => array(
'new-1' => array(
'Title' => 'MyTestWidget',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$request = new SS_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->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
protected static $extra_dataobjects = [
FakePage::class,
TestWidget::class,
];
$form->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
protected $usesDatabase = true;
$this->assertEquals($page->BottomBar()->Widgets()->Count(), 1);
$this->assertEquals($page->SideBar()->Widgets()->Count(), 0);
}
protected static $required_extensions = [
SiteTree::class => [WidgetPageExtension::class]
];
function testFillingTwoAreas() {
$data = 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
)
)
)
);
$request = new SS_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->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
public function testFillingOneArea()
{
$data = array(
'Widget' => array(
'BottomBar' => array(
'new-1' => array(
'Title' => 'MyTestWidget',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$request = new HTTPRequest('get', 'post', array(), $data);
$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');
}
function testDeletingOneWidgetFromOneArea() {
// First get some widgets in there
$data = 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
)
)
)
);
$request = new SS_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->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(
$controller = new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$controller->setRequest($request);
$controller->setRequest($request);
$form->setController($controller);
$form->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)
$data = array(
'Widget' => array(
'SideBar' => array(
),
'BottomBar' => array(
$bottWidgets[0]->ID => array(
'Title' => 'MyTestWidgetBottom',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$form->setRequest($request);
$form->saveInto($page);
$page = new FakePage();
$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);
}
$form->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
function testDeletingAWidgetFromEachArea() {
// First get some widgets in there
$data = 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
)
)
)
);
$request = new SS_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->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
$this->assertEquals($page->BottomBar()->Widgets()->Count(), 1);
$this->assertEquals($page->SideBar()->Widgets()->Count(), 0);
}
$form->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)
$data = array(
'Widget' => array(
'SideBar' => array(
),
'BottomBar' => array(
)
)
);
$request = new SS_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);
}
function testEditingOneWidget() {
// First get some widgets in there
$data = 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
)
)
)
);
$request = new SS_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->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
public function testFillingTwoAreas()
{
$data = 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
)
)
)
);
$request = new HTTPRequest('get', 'post', array(), $data);
$form->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)
$data = 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
)
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$form->setRequest($request);
$form->saveInto($page);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(
$controller = new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$controller->setRequest($request);
$form->setController($controller);
$page = new FakePage();
$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');
}
$form->saveInto($page);
$page->write();
$page->flushCache();
$page->BottomBar()->flushCache();
$page->SideBar()->flushCache();
function testEditingAWidgetFromEachArea() {
// First get some widgets in there
$data = 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
)
)
)
);
$request = new SS_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->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
// Make sure they both got saved
$this->assertEquals($page->BottomBar()->Widgets()->Count(), 1);
$this->assertEquals($page->SideBar()->Widgets()->Count(), 1);
$form->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)
$data = 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
)
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$form->setRequest($request);
$form->saveInto($page);
$sideWidgets = $page->SideBar()->Widgets()->toArray();
$bottWidgets = $page->BottomBar()->Widgets()->toArray();
$this->assertEquals($sideWidgets[0]->getTitle(), 'MyTestWidgetSide');
$this->assertEquals($bottWidgets[0]->getTitle(), 'MyTestWidgetBottom');
}
$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');
}
function testEditAWidgetFromOneAreaAndDeleteAWidgetFromAnotherArea() {
// First get some widgets in there
$data = 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
)
)
)
);
$request = new SS_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->setRequest($request);
$page = new WidgetAreaEditorTest_FakePage();
public function testDeletingOneWidgetFromOneArea()
{
// First get some widgets in there
$data = 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
)
)
)
);
$request = new HTTPRequest('get', 'post', array(), $data);
$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)
$data = array(
'Widget' => array(
'SideBar' => array(
$sideWidgets[0]->ID => array(
'Title' => 'MyTestWidgetSide-edited',
'Type' => $this->widgetToTest,
'Sort' => 0
)
),
'BottomBar' => array(
)
)
);
$request = new SS_HTTPRequest('get', 'post', array(), $data);
$form->setRequest($request);
$form->saveInto($page);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(
$controller = new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$controller->setRequest($request);
$form->setController($controller);
$page = new FakePage();
$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');
}
}
class WidgetAreaEditorTest_FakePage extends Page implements TestOnly {
private static $has_one = array(
"SideBar" => "WidgetArea",
"BottomBar" => "WidgetArea",
);
}
class WidgetAreaEditorTest_TestWidget extends Widget implements TestOnly {
private static $cmsTitle = "Test widget";
private static $title = "Test widget";
private static $description = "Test widget";
$form->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)
$data = array(
'Widget' => array(
'SideBar' => array(
),
'BottomBar' => array(
$bottWidgets[0]->ID => array(
'Title' => 'MyTestWidgetBottom',
'Type' => $this->widgetToTest,
'Sort' => 0
)
)
)
);
$request = new HTTPRequest('get', 'post', array(), $data);
$controller->setRequest($request);
$form->setController($controller);
$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(), 1);
$this->assertEquals($bottWidgets[0]->getTitle(), 'MyTestWidgetBottom');
$this->assertEquals($page->SideBar()->Widgets()->Count(), 0);
}
public function testDeletingAWidgetFromEachArea()
{
// First get some widgets in there
$data = 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
)
)
)
);
$request = new HTTPRequest('get', 'post', array(), $data);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(
$controller = new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$controller->setRequest($request);
$form->setController($controller);
$page = new FakePage();
$form->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)
$data = array(
'Widget' => array(
'SideBar' => array(
),
'BottomBar' => array(
)
)
);
$request = new HTTPRequest('get', 'post', array(), $data);
$controller->setRequest($request);
$form->setController($controller);
$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
$data = 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
)
)
)
);
$request = new HTTPRequest('get', 'post', array(), $data);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(
$controller = new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$controller->setRequest($request);
$form->setController($controller);
$page = new FakePage();
$form->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)
$data = 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
)
)
)
);
$request = new HTTPRequest('get', 'post', array(), $data);
$controller->setRequest($request);
$form->setController($controller);
$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(), 1);
$this->assertEquals($page->SideBar()->Widgets()->Count(), 1);
$this->assertEquals($bottWidgets[0]->getTitle(), 'MyTestWidgetBottom');
$this->assertEquals($sideWidgets[0]->getTitle(), 'MyTestWidgetSide-edited');
}
public function testEditingAWidgetFromEachArea()
{
// First get some widgets in there
$data = 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
)
)
)
);
$request = new HTTPRequest('get', 'post', array(), $data);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(
$controller = new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$controller->setRequest($request);
$form->setController($controller);
$page = new FakePage();
$form->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)
$data = 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
)
)
)
);
$request = new HTTPRequest('get', 'post', array(), $data);
$controller->setRequest($request);
$form->setController($controller);
$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(), 1);
$this->assertEquals($page->SideBar()->Widgets()->Count(), 1);
$this->assertEquals($bottWidgets[0]->getTitle(), 'MyTestWidgetBottom-edited');
$this->assertEquals($sideWidgets[0]->getTitle(), 'MyTestWidgetSide-edited');
}
public function testEditAWidgetFromOneAreaAndDeleteAWidgetFromAnotherArea()
{
// First get some widgets in there
$data = 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
)
)
)
);
$request = new HTTPRequest('get', 'post', array(), $data);
$editorSide = new WidgetAreaEditor('SideBar');
$editorBott = new WidgetAreaEditor('BottomBar');
$form = new Form(
$controller = new ContentController(),
Form::class,
new FieldList($editorSide, $editorBott),
new FieldList()
);
$controller->setRequest($request);
$form->setController($controller);
$page = new 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)
$data = array(
'Widget' => array(
'SideBar' => array(
$sideWidgets[0]->ID => array(
'Title' => 'MyTestWidgetSide-edited',
'Type' => $this->widgetToTest,
'Sort' => 0
)
),
'BottomBar' => array(
)
)
);
$request = new HTTPRequest('get', 'post', array(), $data);
$controller->setRequest($request);
$form->setController($controller);
$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(), 1);
$this->assertEquals($sideWidgets[0]->getTitle(), 'MyTestWidgetSide-edited');
}
}

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,13 @@
<?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 = 'WidgetAreaEditorTest_TestWidget';
private static $cmsTitle = "Test widget";
private static $description = "Test widget";
}

View File

@ -1,94 +1,61 @@
<?php
/**
* @package widgets
* @subpackage tests
*/
class WidgetControllerTest extends FunctionalTest {
protected static $fixture_file = 'WidgetControllerTest.yml';
namespace SilverStripe\Widgets\Tests;
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');
$response = $this->get($page->URLSegment);
$response = $this->submitForm('Form_Form', null, array('TestValue'=>'Updated'));
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\Widgets\Tests\WidgetControllerTest\TestPage;
use SilverStripe\Widgets\Tests\WidgetControllerTest\TestWidget;
$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 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'
);
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
);
}
class WidgetControllerTest extends FunctionalTest
{
protected static $fixture_file = 'WidgetControllerTest.yml';
protected static $extra_dataobjects = [
TestPage::class,
TestWidget::class,
];
protected function setUp(): void
{
parent::setUp();
$this->actWithPermission('ADMIN', function () {
$this->objFromFixture(TestPage::class, 'page1')->publishRecursive();
});
}
public function testWidgetFormRendering()
{
$page = $this->objFromFixture(TestPage::class, 'page1');
$widget = $this->objFromFixture(TestWidget::class, 'widget1');
$response = $this->get($page->URLSegment);
$formAction = sprintf('%s/widget/%d/%s', $page->URLSegment, $widget->ID, 'Form');
$this->assertStringContainsString(
$formAction,
$response->getBody(),
"Widget forms are rendered through WidgetArea templates"
);
}
public function testWidgetFormSubmission()
{
$page = $this->objFromFixture(TestPage::class, 'page1');
$widget = $this->objFromFixture(TestWidget::class, 'widget1');
$this->get($page->URLSegment);
$response = $this->submitForm('Form_Form', null, array('TestValue' => 'Updated'));
$this->assertStringContainsString(
'TestValue: Updated',
$response->getBody(),
"Form values are submitted to correct widget form"
);
$this->assertStringContainsString(
sprintf('Widget ID: %d', $widget->ID),
$response->getBody(),
"Widget form acts on correct widget, as identified in the URL"
);
}
}

View File

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

View File

@ -0,0 +1,20 @@
<?php
namespace SilverStripe\Widgets\Tests\WidgetControllerTest;
use Page;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Widgets\Model\WidgetArea;
class TestPage extends Page implements TestOnly
{
private static $table_name = 'TestPage';
private static $has_one = [
'WidgetControllerTestSidebar' => WidgetArea::class,
];
private static $owns = [
'WidgetControllerTestSidebar',
];
}

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:/tests/WidgetControllerTest']);
return new SSViewer(TestPage::class);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace SilverStripe\Widgets\Tests\WidgetControllerTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Widgets\Model\Widget;
class TestWidget extends Widget implements TestOnly
{
private static $table_name = 'WidgetControllerTest_TestWidget';
private static $db = [
'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\Model\WidgetController;
/**
* @package widgets
* @subpackage tests
*/
class TestWidgetController extends WidgetController implements TestOnly
{
private static $allowed_actions = array(
'Form'
);
public function Form()
{
$widgetform = new Form(
$this,
__FUNCTION__,
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,27 +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.
*/
function getViewer($action) {
$templates = array('WidgetControllerTestPage');
return new SSViewer($templates);
}
}

View File

@ -1 +0,0 @@
$WidgetControllerTestSidebar

View File

@ -1 +0,0 @@
$Form