Skip to content
This repository
Browse code

Merge branch 'master' of git://github.com/silverstripe/sapphire

Conflicts:
	.gitignore
  • Loading branch information...
commit 3bf5f6be2da553372e4cad3c52c40a33a7b04fbc 2 parents eb5961d + 450bc25
John Milmine authored May 03, 2012

Showing 319 changed files with 9,344 additions and 7,584 deletions. Show diff stats Hide diff stats

  1. 3  .gitignore
  2. 8  README.md
  3. 20  _config.php
  4. 16  _config/PasswordEncryptor.yml
  5. 6  _register_database.php
  6. 8  admin/_config.php
  7. 2  admin/code/CMSBatchAction.php
  8. 26  admin/code/CMSBatchActionHandler.php
  9. 23  admin/code/CMSMenu.php
  10. 1  admin/code/CMSMenuItem.php
  11. 2  admin/code/CMSPreviewable.php
  12. 35  admin/code/CMSProfileController.php
  13. 30  admin/code/GroupImportForm.php
  14. 508  admin/code/LeftAndMain.php
  15. 4  admin/code/LeftAndMainDecorator.php
  16. 2  admin/code/LeftAndMainExtension.php
  17. 36  admin/code/MemberImportForm.php
  18. 371  admin/code/MemberTableField.php
  19. 944  admin/code/ModelAdmin.php
  20. 274  admin/code/SecurityAdmin.php
  21. 65  admin/css/ie7.css
  22. 37  admin/css/ie8.css
  23. 651  admin/css/screen.css
  24. BIN  admin/images/16x16-s8aab2a0ce2.png
  25. BIN  admin/images/24x24-s5aa96abf84.png
  26. BIN  admin/images/arrows.png
  27. BIN  admin/images/btn-icon-s41050dc384.png
  28. BIN  admin/images/btn-icon-s9416553f5b.png
  29. 0  admin/images/{btn_icons → btn-icon}/accept.png
  30. 0  admin/images/{btn_icons → btn-icon}/accept_disabled.png
  31. 0  admin/images/{btn_icons → btn-icon}/add.png
  32. 0  admin/images/{btn_icons → btn-icon}/add_disabled.png
  33. BIN  admin/images/btn-icon/addpage.png
  34. 0  admin/images/{btn_icons → btn-icon}/addpage_disabled.png
  35. 0  admin/images/{btn_icons → btn-icon}/arrow-circle-135-left.png
  36. BIN  admin/images/btn-icon/back.png
  37. BIN  admin/images/btn-icon/back_disabled.png
  38. BIN  admin/images/btn-icon/chain--arrow.png
  39. BIN  admin/images/btn-icon/chain--exclamation.png
  40. BIN  admin/images/btn-icon/chain--minus.png
  41. BIN  admin/images/btn-icon/chain--pencil.png
  42. BIN  admin/images/btn-icon/chain--plus.png
  43. BIN  admin/images/btn-icon/chain-small.png
  44. BIN  admin/images/btn-icon/chain-unchain.png
  45. BIN  admin/images/btn-icon/chain.png
  46. 0  {images/sprites_16x16 → admin/images/btn-icon}/cross-circle.png
  47. 0  {images/sprites_16x16 → admin/images/btn-icon}/cross-circle_disabled.png
  48. 0  admin/images/{btn_icons → btn-icon}/decline.png
  49. 0  admin/images/{btn_icons → btn-icon}/decline_disabled.png
  50. BIN  admin/images/btn-icon/document--pencil.png
  51. BIN  admin/images/btn-icon/download-csv.png
  52. 0  {images/sprites_16x16 → admin/images/btn-icon}/drive-upload.png
  53. 0  {images/sprites_16x16 → admin/images/btn-icon}/drive-upload_disabled.png
  54. BIN  admin/images/btn-icon/grid_print.png
  55. 0  admin/images/{btn_icons → btn-icon}/magnifier.png
  56. 0  {images/sprites_16x16 → admin/images/btn-icon}/minus-circle.png
  57. 0  {images/sprites_16x16 → admin/images/btn-icon}/minus-circle_disabled.png
  58. 0  {images/sprites_16x16 → admin/images/btn-icon}/navigation.png
  59. 0  {images/sprites_16x16 → admin/images/btn-icon}/navigation_disabled.png
  60. 0  {images/sprites_16x16 → admin/images/btn-icon}/network-cloud.png
  61. 0  {images/sprites_16x16 → admin/images/btn-icon}/network-cloud_disabled.png
  62. 0  {images/sprites_16x16 → admin/images/btn-icon}/pencil.png
  63. 0  {images/sprites_16x16 → admin/images/btn-icon}/pencil_disabled.png
  64. 0  {images/sprites_16x16 → admin/images/btn-icon}/plug-disconnect-prohibition.png
  65. 0  {images/sprites_16x16 → admin/images/btn-icon}/plug-disconnect-prohibition_disabled.png
  66. 0  admin/images/{btn_icons → btn-icon}/preview.png
  67. 0  admin/images/{btn_icons → btn-icon}/preview_disabled.png
  68. 0  admin/images/{btn_icons → btn-icon}/settings.png
  69. 0  admin/images/{btn_icons → btn-icon}/settings_disabled.png
  70. 0  admin/images/{btn_icons → btn-icon}/unpublish.png
  71. 0  admin/images/{btn_icons → btn-icon}/unpublish_disabled.png
  72. BIN  admin/images/btn_icons-saaa1989272.png
  73. BIN  admin/images/btn_icons-sb7da7f8cce.png
  74. BIN  admin/images/btn_icons/addpage.png
  75. BIN  admin/images/content-header-tabs-sprite.png
  76. BIN  admin/images/filter-icons.png
  77. 0  admin/images/{menu_icons/16x16-s2ac647f5ef.png → menu-icons/16x16-s170d9d69bb.png}
  78. 0  admin/images/{menu_icons → menu-icons}/16x16/blog.png
  79. 0  admin/images/{menu_icons → menu-icons}/16x16/community.png
  80. 0  admin/images/{menu_icons → menu-icons}/16x16/document.png
  81. 0  admin/images/{menu_icons → menu-icons}/16x16/gears.png
  82. 0  admin/images/{menu_icons → menu-icons}/16x16/home.png
  83. 0  admin/images/{menu_icons → menu-icons}/16x16/information.png
  84. 0  admin/images/{menu_icons → menu-icons}/16x16/network.png
  85. 0  admin/images/{menu_icons → menu-icons}/16x16/pencil.png
  86. 0  admin/images/{menu_icons → menu-icons}/16x16/picture.png
  87. 0  admin/images/{menu_icons → menu-icons}/16x16/pie-chart.png
  88. 0  admin/images/{menu_icons/24x24-s0cb1fe1c77.png → menu-icons/24x24-s546fcae8fd.png}
  89. 0  admin/images/{menu_icons → menu-icons}/24x24/blog.png
  90. 0  admin/images/{menu_icons → menu-icons}/24x24/community.png
  91. 0  admin/images/{menu_icons → menu-icons}/24x24/document.png
  92. 0  admin/images/{menu_icons → menu-icons}/24x24/gears.png
  93. 0  admin/images/{menu_icons → menu-icons}/24x24/home.png
  94. 0  admin/images/{menu_icons → menu-icons}/24x24/information.png
  95. 0  admin/images/{menu_icons → menu-icons}/24x24/network.png
  96. 0  admin/images/{menu_icons → menu-icons}/24x24/pencil.png
  97. 0  admin/images/{menu_icons → menu-icons}/24x24/picture.png
  98. 0  admin/images/{menu_icons → menu-icons}/24x24/pie-chart.png
  99. 0  admin/images/{menu_icons → menu-icons}/README
  100. BIN  admin/images/question.png
  101. BIN  admin/images/spinner.gif
  102. BIN  admin/images/spinner.psd
  103. BIN  admin/images/sprites-32x32-sa4e142f7f0.png
  104. BIN  admin/images/sprites-32x32/blue-document-horizontal.png
  105. BIN  admin/images/sprites-32x32/blue-document-text-image.png
  106. BIN  admin/images/sprites-32x32/blue-document-text.png
  107. BIN  admin/images/sprites-32x32/blue-document.png
  108. BIN  admin/images/sprites-32x32/blue-folder-horizontal.png
  109. BIN  admin/images/sprites-32x32/blue-folder.png
  110. 0  admin/images/{sprites_32x32 → sprites-32x32}/dialog-close-over.png
  111. 0  admin/images/{sprites_32x32 → sprites-32x32}/dialog-close.png
  112. BIN  admin/images/sprites-32x32/document-horizontal.png
  113. BIN  admin/images/sprites-32x32/document-text-image.png
  114. BIN  admin/images/sprites-32x32/document-text.png
  115. BIN  admin/images/sprites-32x32/document.png
  116. BIN  admin/images/sprites-32x32/folder-horizontal.png
  117. BIN  admin/images/sprites-32x32/folder.png
  118. BIN  admin/images/sprites-32x32/image-sunset.png
  119. BIN  admin/images/sprites-32x32/image.png
  120. 0  admin/images/{sprites_32x32 → sprites-32x32}/logout.png
  121. 0  admin/images/{sprites_32x32 → sprites-32x32}/menu-arrow-deselected-down.png
  122. 0  admin/images/{sprites_32x32 → sprites-32x32}/menu-arrow-deselected-up.png
  123. 0  admin/images/{sprites_32x32 → sprites-32x32}/menu-arrow-down.png
  124. 0  admin/images/{sprites_32x32 → sprites-32x32}/menu-arrow-up.png
  125. 0  admin/images/{sprites_32x32 → sprites-32x32}/numeric-label.png
  126. BIN  admin/images/sprites-32x32/script-text.png
  127. BIN  admin/images/sprites-32x32/script.png
  128. BIN  admin/images/sprites-32x32/table.png
  129. BIN  admin/images/textures/cms_content_header.png
  130. BIN  admin/javascript/.DS_Store
  131. 161  admin/javascript/AssetTableField.js
  132. 15  admin/javascript/LeftAndMain.AddForm.js
  133. 73  admin/javascript/LeftAndMain.BatchActions.js
  134. 189  admin/javascript/LeftAndMain.Content.js
  135. 129  admin/javascript/LeftAndMain.EditForm.js
  136. 97  admin/javascript/LeftAndMain.Menu.js
  137. 90  admin/javascript/LeftAndMain.Panel.js
  138. 1  admin/javascript/LeftAndMain.Preview.js
  139. 358  admin/javascript/LeftAndMain.Tree.js
  140. 438  admin/javascript/LeftAndMain.js
  141. 347  admin/javascript/MemberTableField.js
  142. 21  admin/javascript/MemberTableField_popup.js
  143. 180  admin/javascript/ModelAdmin.History.js
  144. 195  admin/javascript/ModelAdmin.js
  145. 65  admin/javascript/SecurityAdmin.js
  146. 235  admin/javascript/lib.js
  147. 22  admin/javascript/ssui.core.js
  148. 89  admin/scss/_ModelAdmin.scss
  149. 18  admin/scss/_SecurityAdmin.scss
  150. 299  admin/scss/_forms.scss
  151. 77  admin/scss/_menu.scss
  152. 11  admin/scss/_mixins.scss
  153. 15  admin/scss/_sprites.scss
  154. 1,086  admin/scss/_style.scss
  155. 131  admin/scss/_tree.scss
  156. 20  admin/scss/_typography.scss
  157. 10  admin/scss/_uitheme.scss
  158. 247  admin/scss/ie7.scss
  159. 119  admin/scss/ie8.scss
  160. 8  admin/scss/screen.scss
  161. 91  admin/scss/themes/_default.scss
  162. 9  admin/templates/CMSBreadcrumbs.ss
  163. 4  admin/templates/CMSGridFieldPopupForms.ss
  164. 7  admin/templates/Includes/BackLink_Button.ss
  165. 6  admin/templates/Includes/CMSLoadingScreen.ss
  166. 8  admin/templates/Includes/LeftAndMain_Content.ss
  167. 76  admin/templates/Includes/LeftAndMain_EditForm.ss
  168. 49  admin/templates/Includes/LeftAndMain_Menu.ss
  169. 2  admin/templates/Includes/LeftAndMain_SilverStripeNavigator.ss
  170. 62  admin/templates/Includes/MemberTableField.ss
  171. 50  admin/templates/Includes/ModelAdmin_Content.ss
  172. 5  admin/templates/Includes/ModelAdmin_Results.ss
  173. 14  admin/templates/Includes/ModelAdmin_Tools.ss
  174. 19  admin/templates/Includes/SecurityAdmin_Content.ss
  175. 19  admin/templates/LeftAndMain.ss
  176. 4  admin/templates/ModelSidebar.ss
  177. 2  admin/tests/CMSMenuTest.php
  178. 2  admin/tests/LeftAndMainTest.php
  179. 139  admin/tests/MemberTableFieldTest.php
  180. 31  admin/tests/MemberTableFieldTest.yml
  181. 51  admin/tests/ModelAdminTest.php
  182. 61  admin/tests/SecurityAdminTest.php
  183. 2  admin/thirdparty/chosen/.piston.yml
  184. 24  admin/thirdparty/chosen/Cakefile
  185. 20  admin/thirdparty/chosen/README.md
  186. 2  admin/thirdparty/chosen/VERSION
  187. BIN  admin/thirdparty/chosen/chosen/chosen-sprite.png
  188. 203  admin/thirdparty/chosen/chosen/chosen.css
  189. 302  admin/thirdparty/chosen/chosen/chosen.jquery.js
  190. 6  admin/thirdparty/chosen/chosen/chosen.jquery.min.js
  191. 824  admin/thirdparty/chosen/chosen/chosen.js
  192. 286  admin/thirdparty/chosen/chosen/chosen.proto.js
  193. 6  admin/thirdparty/chosen/chosen/chosen.proto.min.js
  194. 35  admin/thirdparty/chosen/coffee/chosen.jquery.coffee
  195. 13  admin/thirdparty/chosen/coffee/chosen.proto.coffee
  196. 2  admin/thirdparty/chosen/coffee/lib/abstract-chosen.coffee
  197. 480  admin/thirdparty/chosen/example.proto.html
  198. 18  admin/thirdparty/chosen/package.json
  199. 5  admin/thirdparty/history-js/scripts/uncompressed/history.html4.js
  200. 2  admin/thirdparty/history-js/tests.src/each.php
  201. 4  admin/thirdparty/jlayout/lib/jquery.jlayout.js
  202. 7  api/DataFormatter.php
  203. 3  api/FormEncodedDataFormatter.php
  204. 3  api/JSONDataFormatter.php
  205. 5  api/RSSFeed.php
  206. 10  api/RestfulServer.php
  207. 9  api/RestfulService.php
  208. 3  api/SOAPModelAccess.php
  209. 4  api/SapphireSoapServer.php
  210. 8  api/VersionedRestfulServer.php
  211. 4  api/XMLDataFormatter.php
  212. 4  cache/Cache.php
  213. 8  cli-script.php
  214. 4  cli/CliController.php
  215. 14  conf/ConfigureFromEnv.php
  216. 10  control/ContentNegotiator.php
  217. 49  control/Controller.php
  218. 19  control/Cookie.php
  219. 65  control/Director.php
  220. 273  control/FormResponse.php
  221. 8  control/HTTP.php
  222. 16  control/HTTPRequest.php
  223. 4  control/HTTPResponse.php
  224. 4  control/NullHTTPRequest.php
  225. 69  control/PjaxResponseNegotiator.php
  226. 50  control/RequestHandler.php
  227. 36  control/Session.php
  228. 2  core/ArrayLib.php
  229. 12  core/ClassInfo.php
  230. 502  core/Config.php
  231. 118  core/Convert.php
  232. 149  core/Core.php
  233. 74  core/DAG.php
  234. 22  core/Diff.php
  235. 20  core/Extension.php
  236. 4  core/HTMLCleaner.php
  237. 380  core/Object.php
  238. 6  core/PaginatedList.php
  239. 38  core/manifest/ClassLoader.php
  240. 49  core/manifest/ClassManifest.php
  241. 497  core/manifest/ConfigManifest.php
  242. 8  core/manifest/ManifestFileFinder.php
  243. 4  core/manifest/TemplateLoader.php
  244. 6  core/manifest/TemplateManifest.php
  245. 8  core/manifest/TokenisedRegularExpression.php
  246. 33  css/AssetUploadField.css
  247. 3  css/CodeViewer.css
  248. 4  css/FileIFrameField.css
  249. 138  css/GridField.css
  250. 7  css/GridField_print.css
  251. 12  css/HasManyFileField.css
  252. 3  css/TestViewer.css
  253. 4  css/TreeDropdownField.css
  254. 34  css/UploadField.css
  255. 10  dev/Backtrace.php
  256. 5  dev/BuildTask.php
  257. 7  dev/BulkLoader.php
  258. 4  dev/CSSContentParser.php
  259. 3  dev/CSVParser.php
  260. 3  dev/Cli.php
  261. 3  dev/CliDebugView.php
  262. 40  dev/CliTestReporter.php
  263. 351  dev/CodeViewer.php
  264. 5  dev/CsvBulkLoader.php
  265. 7  dev/Debug.php
  266. 23  dev/DebugView.php
  267. 6  dev/Deprecation.php
  268. 35  dev/DevelopmentAdmin.php
  269. 4  dev/FunctionalTest.php
  270. 3  dev/InstallerTest.php
  271. 4  dev/JSTestRunner.php
  272. 4  dev/Log.php
  273. 4  dev/LogEmailWriter.php
  274. 4  dev/LogErrorEmailFormatter.php
  275. 4  dev/LogErrorFileFormatter.php
  276. 4  dev/LogFileWriter.php
  277. 3  dev/MigrationTask.php
  278. 214  dev/ModelViewer.php
  279. 3  dev/Profiler.php
  280. 18  dev/SapphireInfo.php
  281. 7  dev/SapphireREPL.php
  282. 45  dev/SapphireTest.php
  283. 19  dev/SapphireTestReporter.php
  284. 3  dev/SapphireTestSuite.php
  285. 4  dev/SysLogWriter.php
  286. 8  dev/TaskRunner.php
  287. 4  dev/TestListener.php
  288. 4  dev/TestMailer.php
  289. 3  dev/TestOnly.php
  290. 48  dev/TestRunner.php
  291. 6  dev/TestSession.php
  292. 257  dev/TestViewer.php
  293. 2  dev/YamlFixture.php
  294. 4  dev/ZendLog.php
  295. 2  dev/install/DatabaseAdapterRegistry.php
  296. 4  dev/install/DatabaseConfigurationHelper.php
  297. 28  dev/install/MySQLDatabaseConfigurationHelper.php
  298. 112  dev/install/config-form.html
  299. 15  dev/install/config.rb
  300. 101  dev/install/css/install.css
  301. BIN  dev/install/images/ss-logo.png
  302. 302  dev/install/install.css
  303. 2  dev/install/install.js
  304. 15  dev/install/install.php
  305. 301  dev/install/install.php5
  306. 33  dev/install/php5-required.html
  307. 571  dev/install/scss/install.scss
  308. 24  dev/phpunit/PhpUnitWrapper.php
  309. 17  dev/phpunit/PhpUnitWrapper_3_4.php
  310. 14  dev/phpunit/PhpUnitWrapper_3_5.php
  311. BIN  docs/en/_images/page_node_deleted_as_normal.png
  312. BIN  docs/en/_images/page_node_normal.png
  313. BIN  docs/en/_images/page_node_removed.png
  314. BIN  docs/en/_images/page_node_scheduled.png
  315. BIN  docs/en/_images/sss.png
  316. BIN  docs/en/_images/tree_node.png
  317. 497  docs/en/changelogs/3.0.0.md
  318. 116  docs/en/changelogs/alpha/3.0.0-alpha1.md
3  .gitignore
@@ -3,3 +3,6 @@
3 3
 /.buildpath
4 4
 /.project
5 5
 /.settings
  6
+css/GridField_print.css
  7
+admin/thirdparty/chosen/node_modules
  8
+
8  README.md
Source Rendered
@@ -5,13 +5,13 @@ Requires a [`silverstripe-installer`](http://github.com/silverstripe/silverstrip
5 5
 
6 6
 ## Installation ##
7 7
 
8  
-See [installation on different platforms](http://doc.silverstripe.org/sapphire/en/installation/),
9  
-and [installation from source](http://doc.silverstripe.org/sapphire/en/installation/from-source).
  8
+See [installation on different platforms](http://doc.silverstripe.org/framework/en/installation/),
  9
+and [installation from source](http://doc.silverstripe.org/framework/en/installation/from-source).
10 10
 
11 11
 ## Links ##
12 12
 
13  
- * [Requirements](http://doc.silverstripe.org/sapphire/en/installation/server-requirements)
14  
- * [Changelogs](http://doc.silverstripe.org/sapphire/en/changelogs/)
  13
+ * [Requirements](http://doc.silverstripe.org/framework/en/installation/server-requirements)
  14
+ * [Changelogs](http://doc.silverstripe.org/framework/en/changelogs/)
15 15
  * [Bugtracker](http://open.silverstripe.org)
16 16
  * [Forums](http://silverstripe.org/forums)
17 17
  * [Developer Mailinglist](https://groups.google.com/forum/#!forum/silverstripe-dev)
20  _config.php
... ...
@@ -1,9 +1,9 @@
1 1
 <?php
2 2
 
3 3
 /**
4  
- * Sapphire configuration file
  4
+ * Framework configuration file
5 5
  *
6  
- * Here you can make different settings for the Sapphire module (the core
  6
+ * Here you can make different settings for the Framework module (the core
7 7
  * module).
8 8
  *
9 9
  * For example you can register the authentication methods you wish to use
@@ -13,7 +13,7 @@
13 13
  * Authenticator::register_authenticator('OpenIDAuthenticator');
14 14
  * </code>
15 15
  *
16  
- * @package sapphire
  16
+ * @package framework
17 17
  * @subpackage core
18 18
  */
19 19
 
@@ -42,12 +42,12 @@
42 42
 Object::useCustomClass('SSDatetime', 'SS_Datetime', true);
43 43
 Object::useCustomClass('Datetime',   'SS_Datetime', true);
44 44
 
45  
-
46  
-
47 45
 /**
48 46
  * The root directory of TinyMCE
49 47
  */
50  
-define('MCE_ROOT', 'sapphire/thirdparty/tinymce/');
  48
+define('MCE_ROOT', FRAMEWORK_DIR . '/thirdparty/tinymce/');
  49
+
  50
+ShortcodeParser::get('default')->register('file_link', array('File', 'link_shortcode_handler'));
51 51
 
52 52
 /**
53 53
  * The secret key that needs to be sent along with pings to /Email_BounceHandler
@@ -61,12 +61,6 @@
61 61
 	define('EMAIL_BOUNCEHANDLER_KEY', '1aaaf8fb60ea253dbf6efa71baaacbb3');
62 62
 }
63 63
 
64  
-PasswordEncryptor::register('none', 'PasswordEncryptor_None');
65  
-PasswordEncryptor::register('md5', 'PasswordEncryptor_LegacyPHPHash("md5")');
66  
-PasswordEncryptor::register('sha1','PasswordEncryptor_LegacyPHPHash("sha1")');
67  
-PasswordEncryptor::register('md5_v2.4', 'PasswordEncryptor_PHPHash("md5")');
68  
-PasswordEncryptor::register('sha1_v2.4','PasswordEncryptor_PHPHash("sha1")');
69  
-
70 64
 // Zend_Cache temp directory setting
71 65
 $_ENV['TMPDIR'] = TEMP_FOLDER; // for *nix
72 66
 $_ENV['TMP'] = TEMP_FOLDER; // for Windows
@@ -81,4 +75,4 @@
81 75
 Deprecation::notification_version('3.0.0');
82 76
 
83 77
 // TODO Remove once new ManifestBuilder with submodule support is in place
84  
-require_once('admin/_config.php');
  78
+require_once('admin/_config.php');
16  _config/PasswordEncryptor.yml
... ...
@@ -0,0 +1,16 @@
  1
+name: PasswordEncryptor
  2
+---
  3
+PasswordEncryptor:
  4
+  encryptors:
  5
+    none:
  6
+      PasswordEncryptor_None: 
  7
+    md5:
  8
+      PasswordEncryptor_LegacyPHPHash: md5
  9
+    sha1:
  10
+      PasswordEncryptor_LegacyPHPHash: sha1
  11
+    md5_v2.4:
  12
+      PasswordEncryptor_PHPHash: md5
  13
+    sha1_v2.4:
  14
+      PasswordEncryptor_PHPHash: sha1
  15
+    blowfish:
  16
+      PasswordEncryptor_Blowfish:
6  _register_database.php
... ...
@@ -1,12 +1,14 @@
1 1
 <?php
2 2
 
3 3
 // Register the SilverStripe provided databases
  4
+$frameworkPath = defined('FRAMEWORK_PATH') ? FRAMEWORK_PATH : FRAMEWORK_NAME;
  5
+
4 6
 DatabaseAdapterRegistry::register(
5 7
 	array(
6 8
 		'class' => 'MySQLDatabase',
7 9
 		'title' => 'MySQL 5.0+',
8  
-		'helperPath' => 'sapphire/dev/install/MySQLDatabaseConfigurationHelper.php',
9  
-		'supported' => function_exists('mysql_connect'),
  10
+		'helperPath' => $frameworkPath . '/dev/install/MySQLDatabaseConfigurationHelper.php',
  11
+		'supported' => class_exists('MySQLi'),
10 12
 	)
11 13
 );
12 14
 
8  admin/_config.php
@@ -18,17 +18,16 @@
18 18
 	'body_class' => 'typography',
19 19
 	'document_base_url' => Director::absoluteBaseURL(),
20 20
 
21  
-	'urlconverter_callback' => "nullConverter",
22  
-	'setupcontent_callback' => "sapphiremce_setupcontent",
23 21
 	'cleanup_callback' => "sapphiremce_cleanup",
24 22
 
25 23
 	'use_native_selects' => true, // fancy selects are bug as of SS 2.3.0
26 24
 	'valid_elements' => "@[id|class|style|title],#a[id|rel|rev|dir|tabindex|accesskey|type|name|href|target|title|class],-strong/-b[class],-em/-i[class],-strike[class],-u[class],#p[id|dir|class|align|style],-ol[class],-ul[class],-li[class],br,img[id|dir|longdesc|usemap|class|src|border|alt=|title|width|height|align],-sub[class],-sup[class],-blockquote[dir|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|dir|id|style],-tr[id|dir|class|rowspan|width|height|align|valign|bgcolor|background|bordercolor|style],tbody[id|class|style],thead[id|class|style],tfoot[id|class|style],#td[id|dir|class|colspan|rowspan|width|height|align|valign|scope|style],-th[id|dir|class|colspan|rowspan|width|height|align|valign|scope|style],caption[id|dir|class],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align],address[class|align],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|dir|class|align|style],hr[class],dd[id|class|title|dir],dl[id|class|title|dir],dt[id|class|title|dir],@[id,style,class]",
27  
-	'extended_valid_elements' => "img[class|src|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name|usemap],iframe[src|name|width|height|align|frameborder|marginwidth|marginheight|scrolling],object[width|height|data|type],param[name|value],map[class|name|id],area[shape|coords|href|target|alt]"
  25
+	'extended_valid_elements' => "img[class|src|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name|usemap],iframe[src|name|width|height|align|frameborder|marginwidth|marginheight|scrolling],object[width|height|data|type],param[name|value],map[class|name|id],area[shape|coords|href|target|alt]",
  26
+	'spellchecker_rpc_url' => THIRDPARTY_DIR . '/tinymce-spellchecker/rpc.php'
28 27
 ));
29 28
 
30 29
 HtmlEditorConfig::get('cms')->enablePlugins('media', 'fullscreen');
31  
-HtmlEditorConfig::get('cms')->enablePlugins(array('ssbuttons' => '../../../cms/javascript/tinymce_ssbuttons/editor_plugin_src.js'));
  30
+HtmlEditorConfig::get('cms')->enablePlugins(array('ssbuttons' => sprintf('../../../%s/tinymce_ssbuttons/editor_plugin_src.js', THIRDPARTY_DIR)));
32 31
 			
33 32
 HtmlEditorConfig::get('cms')->insertButtonsBefore('formatselect', 'styleselect');
34 33
 HtmlEditorConfig::get('cms')->addButtonsToLine(2, 'ssimage', 'ssflash', 'sslink', 'unlink', 'anchor', 'separator','code', 'fullscreen', 'separator');
@@ -36,3 +35,4 @@
36 35
 HtmlEditorConfig::get('cms')->removeButtons('tablecontrols');
37 36
 HtmlEditorConfig::get('cms')->addButtonsToLine(3, 'tablecontrols');
38 37
 
  38
+CMSMenu::remove_menu_item('CMSProfileController');
2  admin/code/CMSBatchAction.php
@@ -146,4 +146,4 @@ function getParameterFields() {
146 146
 	function canView() {
147 147
 		return true;
148 148
 	}
149  
-}
  149
+}
26  admin/code/CMSBatchActionHandler.php
@@ -68,7 +68,7 @@ function Link() {
68 68
 
69 69
 	function handleAction($request) {
70 70
 		// This method can't be called without ajax.
71  
-		if(!$this->parentController->isAjax()) {
  71
+		if(!$request->isAjax()) {
72 72
 			$this->parentController->redirectBack();
73 73
 			return;
74 74
 		}
@@ -81,7 +81,7 @@ function handleAction($request) {
81 81
 		$actionHandler = new $actionClass();
82 82
 		
83 83
 		// Sanitise ID list and query the database for apges
84  
-		$ids = split(' *, *', trim($request->requestVar('csvIDs')));
  84
+		$ids = preg_split('/ *, */', trim($request->requestVar('csvIDs')));
85 85
 		foreach($ids as $k => $v) if(!is_numeric($v)) unset($ids[$k]);
86 86
 		
87 87
 		if($ids) {
@@ -111,8 +111,14 @@ function handleAction($request) {
111 111
 						implode(", ", $idsFromLive)
112 112
 					);
113 113
 					$livePages = Versioned::get_by_stage($this->recordClass, 'Live', $sql);
114  
-					if($pages) $pages->merge($livePages);
115  
-					else $pages = $livePages;
  114
+					if($pages) {
  115
+						// Can't merge into a DataList, need to condense into an actual list first
  116
+						// (which will retrieve all records as objects, so its an expensive operation)
  117
+						$pages = new ArrayList($pages->toArray());
  118
+						$pages->merge($livePages);
  119
+					}	else {
  120
+						$pages = $livePages;
  121
+					}
116 122
 				}
117 123
 			}
118 124
 		} else {
@@ -124,12 +130,12 @@ function handleAction($request) {
124 130
 
125 131
 	function handleApplicablePages($request) {
126 132
 		// Find the action handler
127  
-		$actions = Object::get_static($this->class, 'batch_actions');
  133
+		$actions = Config::inst()->get($this->class, 'batch_actions', Config::FIRST_SET);
128 134
 		$actionClass = $actions[$request->param('BatchAction')];
129 135
 		$actionHandler = new $actionClass['class']();
130 136
 
131 137
 		// Sanitise ID list and query the database for apges
132  
-		$ids = split(' *, *', trim($request->requestVar('csvIDs')));
  138
+		$ids = preg_split('/ *, */', trim($request->requestVar('csvIDs')));
133 139
 		foreach($ids as $k => $id) $ids[$k] = (int)$id;
134 140
 		$ids = array_filter($ids);
135 141
 		
@@ -146,12 +152,12 @@ function handleApplicablePages($request) {
146 152
 	
147 153
 	function handleConfirmation($request) {
148 154
 		// Find the action handler
149  
-		$actions = Object::get_static($this->class, 'batch_actions');
  155
+		$actions = Config::inst()->get($this->class, 'batch_actions', Config::FIRST_SET);
150 156
 		$actionClass = $actions[$request->param('BatchAction')];
151 157
 		$actionHandler = new $actionClass();
152 158
 
153 159
 		// Sanitise ID list and query the database for apges
154  
-		$ids = split(' *, *', trim($request->requestVar('csvIDs')));
  160
+		$ids = preg_split('/ *, */', trim($request->requestVar('csvIDs')));
155 161
 		foreach($ids as $k => $id) $ids[$k] = (int)$id;
156 162
 		$ids = array_filter($ids);
157 163
 		
@@ -197,7 +203,7 @@ function batchActionList() {
197 203
 	 * @return array See {@link register()} for the returned format.
198 204
 	 */
199 205
 	function batchActions() {
200  
-		$actions = Object::get_static($this->class, 'batch_actions');
  206
+		$actions = Config::inst()->get($this->class, 'batch_actions', Config::FIRST_SET);
201 207
 		if($actions) foreach($actions as $action) {
202 208
 			if($action['recordClass'] != $this->recordClass) unset($action);
203 209
 		}
@@ -205,4 +211,4 @@ function batchActions() {
205 211
 		return $actions;
206 212
 	}
207 213
 
208  
-}
  214
+}
23  admin/code/CMSMenu.php
@@ -59,9 +59,9 @@ public static function add_controller($controllerClass) {
59 59
 	 * Return a CMSMenuItem to add the given controller to the CMSMenu
60 60
 	 */
61 61
 	protected static function menuitem_for_controller($controllerClass) {
62  
-		$urlBase      = Object::get_static($controllerClass, 'url_base');
63  
-		$urlSegment   = Object::get_static($controllerClass, 'url_segment');
64  
-		$menuPriority = Object::get_static($controllerClass, 'menu_priority');
  62
+		$urlBase      = Config::inst()->get($controllerClass, 'url_base', Config::FIRST_SET);
  63
+		$urlSegment   = Config::inst()->get($controllerClass, 'url_segment', Config::FIRST_SET);
  64
+		$menuPriority = Config::inst()->get($controllerClass, 'menu_priority', Config::FIRST_SET);
65 65
 		
66 66
 		// Don't add menu items defined the old way
67 67
 		if($urlSegment === null && $controllerClass != "CMSMain") return;
@@ -81,12 +81,12 @@ protected static function menuitem_for_controller($controllerClass) {
81 81
 	 * Add the appropriate Director rules for the given controller.
82 82
 	 */
83 83
 	protected static function add_director_rule_for_controller($controllerClass) {
84  
-		$urlBase      = Object::get_static($controllerClass, 'url_base');
85  
-		$urlSegment   = Object::get_static($controllerClass, 'url_segment');
86  
-		$urlRule      = Object::get_static($controllerClass, 'url_rule');
87  
-		$urlPriority  = Object::get_static($controllerClass, 'url_priority');
88  
-		
89  
-		if($urlSegment || $controllerClass == "CMSMain") {
  84
+		$urlBase      = Config::inst()->get($controllerClass, 'url_base', Config::FIRST_SET);
  85
+		$urlSegment   = Config::inst()->get($controllerClass, 'url_segment', Config::FIRST_SET);
  86
+		$urlRule      = Config::inst()->get($controllerClass, 'url_rule', Config::FIRST_SET);
  87
+		$urlPriority  = Config::inst()->get($controllerClass, 'url_priority', Config::FIRST_SET);
  88
+
  89
+		if($urlSegment || $controllerClass == 'CMSMain') {
90 90
 			$link = Controller::join_links($urlBase, $urlSegment) . '/';
91 91
 		
92 92
 			// Make director rule
@@ -101,7 +101,7 @@ protected static function add_director_rule_for_controller($controllerClass) {
101 101
 	/**
102 102
 	 * Add an arbitrary URL to the CMS menu.
103 103
 	 *
104  
-	 * @param string $code A unique identifier (used to create a CSS ID and as it's key in {@link $menu_items}
  104
+	 * @param string $code A unique identifier (used to create a CSS ID and its key in {@link $menu_items})
105 105
 	 * @param string $menuTitle The link's title in the CMS menu
106 106
 	 * @param string $url The url of the link
107 107
 	 * @param integer $priority The menu priority (sorting order) of the menu item.  Higher priorities will be further left.
@@ -318,9 +318,8 @@ function provideI18nEntities() {
318 318
 		foreach($cmsClasses as $cmsClass) {
319 319
 			$defaultTitle = LeftAndMain::menu_title_for_class($cmsClass);
320 320
 			$ownerModule = i18n::get_owner_module($cmsClass);
321  
-			$entities["{$cmsClass}.MENUTITLE"] = array($defaultTitle, PR_HIGH, 'Menu title', $ownerModule);
  321
+			$entities["{$cmsClass}.MENUTITLE"] = array($defaultTitle, 'Menu title', $ownerModule);
322 322
 		}
323 323
 		return $entities;
324 324
 	}
325 325
 }
326  
-?>
1  admin/code/CMSMenuItem.php
@@ -47,4 +47,3 @@ public function __construct($title, $url, $controller = null, $priority = -1) {
47 47
 	}
48 48
 	
49 49
 }
50  
-?>
2  admin/code/CMSPreviewable.php
@@ -22,4 +22,4 @@ function Link();
22 22
 	 */
23 23
 	function CMSEditLink();
24 24
 
25  
-}
  25
+}
35  admin/code/CMSProfileController.php
... ...
@@ -0,0 +1,35 @@
  1
+<?php
  2
+class CMSProfileController extends LeftAndMain {
  3
+
  4
+	static $url_segment = 'myprofile';
  5
+	static $required_permission_codes = false;
  6
+
  7
+	public function index($request) {
  8
+		$form = $this->Member_ProfileForm();
  9
+		return $this->customise(array(
  10
+			'Content' => ' ',
  11
+			'Form' => $form
  12
+		))->renderWith('CMSDialog');
  13
+	}
  14
+	
  15
+	public function Member_ProfileForm() {
  16
+		return new Member_ProfileForm($this, 'Member_ProfileForm', Member::currentUser());
  17
+	}
  18
+
  19
+	function canView($member = null) {
  20
+		if(!$member && $member !== FALSE) $member = Member::currentUser();
  21
+		
  22
+		// cms menus only for logged-in members
  23
+		if(!$member) return false;
  24
+		
  25
+		// Only check for generic CMS permissions
  26
+		if(
  27
+			!Permission::checkMember($member, "CMS_ACCESS_LeftAndMain")
  28
+			&& !Permission::checkMember($member, "CMS_ACCESS_CMSMain")
  29
+		) {
  30
+			return false;
  31
+		}
  32
+		
  33
+		return true;
  34
+	}
  35
+}
30  admin/code/GroupImportForm.php
@@ -49,13 +49,16 @@ function __construct($controller, $name, $fields = null, $actions = null, $valid
49 49
 		}
50 50
 		
51 51
 		if(!$actions) $actions = new FieldList(
52  
-			new FormAction('doImport', _t('SecurityAdmin_MemberImportForm.BtnImport', 'Import'))
  52
+			$importAction = new FormAction('doImport', _t('SecurityAdmin_MemberImportForm.BtnImport', 'Import from CSV'))
53 53
 		);
54  
-		
  54
+
  55
+		$importAction->addExtraClass('ss-ui-button');
  56
+
55 57
 		if(!$validator) $validator = new RequiredFields('CsvFile');
56 58
 		
57 59
 		parent::__construct($controller, $name, $fields, $actions, $validator);
58  
-		
  60
+
  61
+		$this->addExtraClass('cms');
59 62
 		$this->addExtraClass('import-form');
60 63
 	}
61 64
 	
@@ -67,24 +70,23 @@ function doImport($data, $form) {
67 70
 		
68 71
 		// result message
69 72
 		$msgArr = array();
70  
-		if($result->CreatedCount()) $msgArr[] = sprintf(
71  
-			_t('GroupImportForm.ResultCreated', 'Created %d groups'),
72  
-			$result->CreatedCount()
  73
+		if($result->CreatedCount()) $msgArr[] = _t(
  74
+			'GroupImportForm.ResultCreated', 'Created {count} groups',
  75
+			array('count' => $result->CreatedCount())
73 76
 		);
74  
-		if($result->UpdatedCount()) $msgArr[] = sprintf(
75  
-			_t('GroupImportForm.ResultUpdated', 'Updated %d groups'),
76  
-			$result->UpdatedCount()
  77
+		if($result->UpdatedCount()) $msgArr[] = _t(
  78
+			'GroupImportForm.ResultUpdated', 'Updated %d groups',
  79
+			array('count' => $result->UpdatedCount())
77 80
 		);
78  
-		if($result->DeletedCount()) $msgArr[] = sprintf(
79  
-			_t('GroupImportForm.ResultDeleted', 'Deleted %d groups'),
80  
-			$result->DeletedCount()
  81
+		if($result->DeletedCount()) $msgArr[] = _t(
  82
+			'GroupImportForm.ResultDeleted', 'Deleted %d groups',
  83
+			array('count' => $result->DeletedCount())
81 84
 		);
82 85
 		$msg = ($msgArr) ? implode(',', $msgArr) : _t('MemberImportForm.ResultNone', 'No changes');
83 86
 	
84 87
 		$this->sessionMessage($msg, 'good');
85 88
 		
86  
-		$this->redirectBack();
  89
+		$this->controller->redirectBack();
87 90
 	}
88 91
 	
89 92
 }
90  
-?>
508  admin/code/LeftAndMain.php
@@ -9,7 +9,7 @@
9 9
  * @package cms
10 10
  * @subpackage core
11 11
  */
12  
-class LeftAndMain extends Controller {
  12
+class LeftAndMain extends Controller implements PermissionProvider {
13 13
 	
14 14
 	/**
15 15
 	 * The 'base' url for CMS administration areas.
@@ -73,18 +73,24 @@ class LeftAndMain extends Controller {
73 73
 		'savetreenode',
74 74
 		'getitem',
75 75
 		'getsubtree',
76  
-		'myprofile',
77 76
 		'printable',
78 77
 		'show',
79  
-		'Member_ProfileForm',
80 78
 		'EditorToolbar',
81 79
 		'EditForm',
82  
-		'RootForm',
83 80
 		'AddForm',
84 81
 		'batchactions',
85 82
 		'BatchActionsForm',
86 83
 		'Member_ProfileForm',
87 84
 	);
  85
+
  86
+	/**
  87
+	 * @var Array Codes which are required from the current user to view this controller.
  88
+	 * If multiple codes are provided, all of them are required.
  89
+	 * All CMS controllers require "CMS_ACCESS_LeftAndMain" as a baseline check,
  90
+	 * and fall back to "CMS_ACCESS_<class>" if no permissions are defined here.
  91
+	 * See {@link canView()} for more details on permission checks.
  92
+	 */
  93
+	static $required_permission_codes;
88 94
 	
89 95
 	/**
90 96
 	 * Register additional requirements through the {@link Requirements} class.
@@ -98,16 +104,18 @@ class LeftAndMain extends Controller {
98 104
 		'css' => array(),
99 105
 		'themedcss' => array(),
100 106
 	);
  107
+
  108
+	/**
  109
+	 * @var PJAXResponseNegotiator
  110
+	 */
  111
+	protected $responseNegotiator;
101 112
 	
102 113
 	/**
103 114
 	 * @param Member $member
104  
-	 *
105 115
 	 * @return boolean
106 116
 	 */
107 117
 	function canView($member = null) {
108  
-		if(!$member && $member !== FALSE) {
109  
-			$member = Member::currentUser();
110  
-		}
  118
+		if(!$member && $member !== FALSE) $member = Member::currentUser();
111 119
 		
112 120
 		// cms menus only for logged-in members
113 121
 		if(!$member) return false;
@@ -117,12 +125,18 @@ function canView($member = null) {
117 125
 			$alternateAllowed = $this->alternateAccessCheck();
118 126
 			if($alternateAllowed === FALSE) return false;
119 127
 		}
120  
-			
121  
-		// Default security check for LeftAndMain sub-class permissions
122  
-		if(!Permission::checkMember($member, "CMS_ACCESS_$this->class") && 
123  
-		   !Permission::checkMember($member, "CMS_ACCESS_LeftAndMain")) {
124  
-			return false;
  128
+
  129
+		// Check for "CMS admin" permission
  130
+		if(Permission::checkMember($member, "CMS_ACCESS_LeftAndMain")) return true;
  131
+
  132
+		// Check for LeftAndMain sub-class permissions			
  133
+		$codes = array();
  134
+		$extraCodes = $this->stat('required_permission_codes');
  135
+		if($extraCodes !== false) { // allow explicit FALSE to disable subclass check
  136
+			if($extraCodes) $codes = array_merge($codes, (array)$extraCodes);
  137
+			else $codes[] = "CMS_ACCESS_$this->class";	
125 138
 		}
  139
+		foreach($codes as $code) if(!Permission::checkMember($member, $code)) return false;
126 140
 		
127 141
 		return true;
128 142
 	}
@@ -146,7 +160,7 @@ function init() {
146 160
 		// can't be done in cms/_config.php as locale is not set yet
147 161
 		CMSMenu::add_link(
148 162
 			'Help', 
149  
-			_t('LeftAndMain.HELP', 'Help', PR_HIGH, 'Menu title'), 
  163
+			_t('LeftAndMain.HELP', 'Help', 'Menu title'), 
150 164
 			self::$help_link
151 165
 		);
152 166
 
@@ -184,13 +198,8 @@ function init() {
184 198
 		if(Director::redirected_to()) return;
185 199
 
186 200
 		// Audit logging hook
187  
-		if(empty($_REQUEST['executeForm']) && !$this->isAjax()) $this->extend('accessedCMS');
  201
+		if(empty($_REQUEST['executeForm']) && !$this->request->isAjax()) $this->extend('accessedCMS');
188 202
 		
189  
-		// Requirements
190  
-
191  
-		// Suppress behaviour/prototype validation instructions in CMS, not compatible with ajax loading of forms.
192  
-		Validator::set_javascript_validation_handler('none');
193  
-
194 203
 		// Set the members html editor config
195 204
 		HtmlEditorConfig::set_active(Member::currentUser()->getHtmlEditorConfigForCMS());
196 205
 		
@@ -200,7 +209,7 @@ function init() {
200 209
 		$htmlEditorConfig->setOption('language', i18n::get_tinymce_lang());
201 210
 		if(!$htmlEditorConfig->getOption('content_css')) {
202 211
 			$cssFiles = array();
203  
-			$cssFiles[] = 'sapphire/admin/css/editor.css';
  212
+			$cssFiles[] = FRAMEWORK_ADMIN_DIR . '/css/editor.css';
204 213
 			
205 214
 			// Use theme from the site config
206 215
 			if(class_exists('SiteConfig') && ($config = SiteConfig::current_site_config()) && $config->Theme) {
@@ -230,40 +239,33 @@ function init() {
230 239
 		Requirements::combine_files(
231 240
 			'lib.js',
232 241
 			array(
233  
-				THIRDPARTY_DIR . '/prototype/prototype.js',
234  
-				THIRDPARTY_DIR . '/behaviour/behaviour.js',
235  
-				SAPPHIRE_DIR . '/javascript/prototype_improvements.js',
236 242
 				THIRDPARTY_DIR . '/jquery/jquery.js',
237  
-				SAPPHIRE_DIR . '/javascript/jquery_improvements.js',
238  
-				THIRDPARTY_DIR . '/jquery-livequery/jquery.livequery.js',
239  
-				SAPPHIRE_DIR . '/javascript/jquery-ondemand/jquery.ondemand.js',
  243
+				FRAMEWORK_DIR . '/javascript/jquery-ondemand/jquery.ondemand.js',
  244
+				FRAMEWORK_ADMIN_DIR . '/javascript/lib.js',
240 245
 				THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js',
241 246
 				THIRDPARTY_DIR . '/json-js/json2.js',
242 247
 				THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js',
243 248
 				THIRDPARTY_DIR . '/jquery-cookie/jquery.cookie.js',
244 249
 				THIRDPARTY_DIR . '/jquery-query/jquery.query.js',
245  
-				SAPPHIRE_ADMIN_DIR . '/thirdparty/jquery-notice/jquery.notice.js',
246  
-				THIRDPARTY_DIR . '/jquery-metadata/jquery.metadata.js',
247  
-				SAPPHIRE_ADMIN_DIR . '/thirdparty/jsizes/lib/jquery.sizes.js',
248  
-				SAPPHIRE_ADMIN_DIR . '/thirdparty/jlayout/lib/jlayout.border.js',
249  
-				SAPPHIRE_ADMIN_DIR . '/thirdparty/jlayout/lib/jquery.jlayout.js',
250  
-				SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.js',
251  
-				SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.adapter.jquery.js',
252  
-				// SAPPHIRE_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.html4.js',
  250
+				THIRDPARTY_DIR . '/jquery-form/jquery.form.js',
  251
+				FRAMEWORK_ADMIN_DIR . '/thirdparty/jquery-notice/jquery.notice.js',
  252
+				FRAMEWORK_ADMIN_DIR . '/thirdparty/jsizes/lib/jquery.sizes.js',
  253
+				FRAMEWORK_ADMIN_DIR . '/thirdparty/jlayout/lib/jlayout.border.js',
  254
+				FRAMEWORK_ADMIN_DIR . '/thirdparty/jlayout/lib/jquery.jlayout.js',
  255
+				FRAMEWORK_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.js',
  256
+				FRAMEWORK_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.adapter.jquery.js',
  257
+				FRAMEWORK_ADMIN_DIR . '/thirdparty/history-js/scripts/uncompressed/history.html4.js',
253 258
 				THIRDPARTY_DIR . '/jstree/jquery.jstree.js',
254  
-				SAPPHIRE_ADMIN_DIR . '/thirdparty/chosen/chosen/chosen.jquery.js',
255  
-				SAPPHIRE_ADMIN_DIR . '/thirdparty/jquery-hoverIntent/jquery.hoverIntent.js',
256  
-				SAPPHIRE_ADMIN_DIR . '/javascript/jquery-changetracker/lib/jquery.changetracker.js',
257  
-				SAPPHIRE_DIR . '/javascript/TreeDropdownField.js',
258  
-				SAPPHIRE_DIR ."/thirdparty/jquery-form/jquery.form.js",
259  
-				SAPPHIRE_DIR . '/javascript/DateField.js',
260  
-				SAPPHIRE_DIR . '/javascript/HtmlEditorField.js',
261  
-				SAPPHIRE_DIR . '/javascript/TabSet.js',
262  
-				SAPPHIRE_DIR . '/javascript/Validator.js',
263  
-				SAPPHIRE_DIR . '/javascript/i18n.js',
264  
-				SAPPHIRE_ADMIN_DIR . '/javascript/ssui.core.js',
265  
-				SAPPHIRE_DIR . '/javascript/tiny_mce_improvements.js',
266  
-				CMS_DIR . '/javascript/ThumbnailStripField.js',
  259
+				FRAMEWORK_ADMIN_DIR . '/thirdparty/chosen/chosen/chosen.jquery.js',
  260
+				FRAMEWORK_ADMIN_DIR . '/thirdparty/jquery-hoverIntent/jquery.hoverIntent.js',
  261
+				FRAMEWORK_ADMIN_DIR . '/javascript/jquery-changetracker/lib/jquery.changetracker.js',
  262
+				FRAMEWORK_DIR . '/javascript/TreeDropdownField.js',
  263
+				FRAMEWORK_DIR . '/javascript/DateField.js',
  264
+				FRAMEWORK_DIR . '/javascript/HtmlEditorField.js',
  265
+				FRAMEWORK_DIR . '/javascript/TabSet.js',
  266
+				FRAMEWORK_DIR . '/javascript/i18n.js',
  267
+				FRAMEWORK_ADMIN_DIR . '/javascript/ssui.core.js',
  268
+				FRAMEWORK_DIR . '/javascript/GridField.js',
267 269
 			)
268 270
 		);
269 271
 		
@@ -273,34 +275,36 @@ function init() {
273 275
 			'leftandmain.js',
274 276
 			array_unique(array_merge(
275 277
 				array(
276  
-					SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.js',
277  
-					SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Panel.js',
278  
-					SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Tree.js',
279  
-					SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Ping.js',
280  
-					SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Content.js',
281  
-					SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.EditForm.js',
282  
-					SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Menu.js',
283  
-					SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.AddForm.js',
284  
-					SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.Preview.js',
285  
-					SAPPHIRE_ADMIN_DIR . '/javascript/LeftAndMain.BatchActions.js',
  278
+					FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.js',
  279
+					FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Panel.js',
  280
+					FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Tree.js',
  281
+					FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Ping.js',
  282
+					FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Content.js',
  283
+					FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.EditForm.js',
  284
+					FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Menu.js',
  285
+					FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.AddForm.js',
  286
+					FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Preview.js',
  287
+					FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.BatchActions.js',
286 288
 				),
287  
-				Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang', true, true),
288  
-				Requirements::add_i18n_javascript(SAPPHIRE_ADMIN_DIR . '/javascript/lang', true, true)
  289
+				Requirements::add_i18n_javascript(FRAMEWORK_DIR . '/javascript/lang', true, true),
  290
+				Requirements::add_i18n_javascript(FRAMEWORK_ADMIN_DIR . '/javascript/lang', true, true)
289 291
 			))
290 292
 		);
291 293
 
  294
+		Requirements::css(FRAMEWORK_ADMIN_DIR . '/thirdparty/jquery-notice/jquery.notice.css');
292 295
 		Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
293  
-		Requirements::css(SAPPHIRE_ADMIN_DIR .'/thirdparty/chosen/chosen/chosen.css');
  296
+		Requirements::css(FRAMEWORK_ADMIN_DIR .'/thirdparty/chosen/chosen/chosen.css');
294 297
 		Requirements::css(THIRDPARTY_DIR . '/jstree/themes/apple/style.css');
295  
-		Requirements::css(SAPPHIRE_DIR . '/css/TreeDropdownField.css');
296  
-		Requirements::css(SAPPHIRE_ADMIN_DIR . '/css/screen.css');
  298
+		Requirements::css(FRAMEWORK_DIR . '/css/TreeDropdownField.css');
  299
+		Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/screen.css');
  300
+		Requirements::css(FRAMEWORK_DIR . '/css/GridField.css');
297 301
 
298 302
 		// Browser-specific requirements
299 303
 		$ie = isset($_SERVER['HTTP_USER_AGENT']) ? strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') : false;
300 304
 		if($ie) {
301 305
 			$version = substr($_SERVER['HTTP_USER_AGENT'], $ie + 5, 3);
302  
-			if($version == 7) Requirements::css('sapphire/admin/css/ie7.css');
303  
-			else if($version == 8) Requirements::css('sapphire/admin/css/ie8.css');
  306
+			if($version == 7) Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/ie7.css');
  307
+			else if($version == 8) Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/ie8.css');
304 308
 		}
305 309
 
306 310
 		// Custom requirements				
@@ -322,7 +326,7 @@ function init() {
322 326
 		SSViewer::set_theme(null);
323 327
 	}
324 328
 	
325  
-	function handleRequest($request, DataModel $model) {
  329
+	function handleRequest(SS_HTTPRequest $request, DataModel $model = null) {
326 330
 		$title = $this->Title();
327 331
 		
328 332
 		$response = parent::handleRequest($request, $model);
@@ -332,8 +336,25 @@ function handleRequest($request, DataModel $model) {
332 336
 		return $response;
333 337
 	}
334 338
 
  339
+	/**
  340
+	 * Overloaded redirection logic to trigger a fake redirect on ajax requests.
  341
+	 * While this violates HTTP principles, its the only way to work around the
  342
+	 * fact that browsers handle HTTP redirects opaquely, no intervention via JS is possible.
  343
+	 * In isolation, that's not a problem - but combined with history.pushState()
  344
+	 * it means we would request the same redirection URL twice if we want to update the URL as well.
  345
+	 * See LeftAndMain.js for the required jQuery ajaxComplete handlers.
  346
+	 */
  347
+	function redirect($url, $code=302) {
  348
+		if($this->request->isAjax()) {
  349
+			$this->response->addHeader('X-ControllerURL', $url);
  350
+			return ''; // Actual response will be re-requested by client
  351
+		} else {
  352
+			parent::redirect($url, $code);
  353
+		}
  354
+	}
  355
+
335 356
 	function index($request) {
336  
-		return ($this->isAjax()) ? $this->show($request) : $this->getViewer('index')->process($this);
  357
+		return $this->getResponseNegotiator()->respond($request);
337 358
 	}
338 359
 
339 360
 	
@@ -368,14 +389,14 @@ public function Link($action = null) {
368 389
 			"$action"
369 390
 		);
370 391
 	}
371  
-	
  392
+
372 393
 	/**
373 394
 	 * Returns the menu title for the given LeftAndMain subclass.
374 395
 	 * Implemented static so that we can get this value without instantiating an object.
375 396
 	 * Menu title is *not* internationalised.
376 397
 	 */
377 398
 	static function menu_title_for_class($class) {
378  
-		$title = eval("return $class::\$menu_title;");
  399
+		$title = Config::inst()->get($class, 'menu_title', Config::FIRST_SET);
379 400
 		if(!$title) $title = preg_replace('/Admin$/', '', $class);
380 401
 		return $title;
381 402
 	}
@@ -383,20 +404,30 @@ static function menu_title_for_class($class) {
383 404
 	public function show($request) {
384 405
 		// TODO Necessary for TableListField URLs to work properly
385 406
 		if($request->param('ID')) $this->setCurrentPageID($request->param('ID'));
386  
-		
387  
-		if($this->isAjax()) {
388  
-			if($request->getVar('cms-view-form')) {
389  
-				$form = $this->getEditForm();
390  
-				$content = $form->forTemplate();
391  
-			} else {
392  
-				// Rendering is handled by template, which will call EditForm() eventually
393  
-				$content = $this->renderWith($this->getTemplatesWithSuffix('_Content'));
394  
-			}
395  
-		} else {
396  
-			$content = $this->renderWith($this->getViewer('show'));
  407
+		return $this->getResponseNegotiator()->respond($request);
  408
+	}
  409
+
  410
+	/**
  411
+	 * Caution: Volatile API.
  412
+	 *  
  413
+	 * @return PJAXResponseNegotiator
  414
+	 */
  415
+	protected function getResponseNegotiator() {
  416
+		if(!$this->responseNegotiator) {
  417
+			$controller = $this;
  418
+			$this->responseNegotiator = new PJAXResponseNegotiator(array(
  419
+				'CurrentForm' => function() use(&$controller) {
  420
+					return $controller->getEditForm()->forTemplate();
  421
+				},
  422
+				'Content' => function() use(&$controller) {
  423
+					return $controller->renderWith($controller->getTemplatesWithSuffix('_Content'));
  424
+				},
  425
+				'default' => function() use(&$controller) {
  426
+					return $controller->renderWith($controller->getViewer('show'));
  427
+				}
  428
+			));
397 429
 		}
398  
-				
399  
-		return $content;
  430
+		return $this->responseNegotiator;
400 431
 	}
401 432
 
402 433
 	//------------------------------------------------------------------------------------------//
@@ -459,7 +490,7 @@ public function MainMenu() {
459 490
 				$menu->push(new ArrayData(array(
460 491
 					"MenuItem" => $menuItem,
461 492
 					"Title" => Convert::raw2xml($title),
462  
-					"Code" => DBField::create('Text', $code),
  493
+					"Code" => DBField::create_field('Text', $code),
463 494
 					"Link" => $menuItem->url,
464 495
 					"LinkingMode" => $linkingmode
465 496
 				)));
@@ -478,10 +509,12 @@ public function Menu() {
478 509
 	/**
479 510
 	 * Return a list of appropriate templates for this class, with the given suffix
480 511
 	 */
481  
-	protected function getTemplatesWithSuffix($suffix) {
  512
+	public function getTemplatesWithSuffix($suffix) {
  513
+		$templates = array();
482 514
 		$classes = array_reverse(ClassInfo::ancestry($this->class));
483 515
 		foreach($classes as $class) {
484  
-			$templates[] = $class . $suffix;
  516
+			$template = $class . $suffix;
  517
+			if(SSViewer::hasTemplate($template)) $templates[] = $template;
485 518
 			if($class == 'LeftAndMain') break;
486 519
 		}
487 520
 		return $templates;
@@ -503,6 +536,40 @@ public function getRecord($id) {
503 536
 			return false;
504 537
 		}
505 538
 	}
  539
+
  540
+	/**
  541
+	 * @return ArrayList
  542
+	 */
  543
+	public function Breadcrumbs($unlinked = false) {
  544
+		$title = self::menu_title_for_class($this->class);
  545
+		$items = new ArrayList(array(
  546
+			new ArrayData(array(
  547
+				'Title' => $title,
  548
+				'Link' => ($unlinked) ? false : $this->Link()
  549
+			))
  550
+		));
  551
+		$record = $this->currentPage();
  552
+		if($record && $record->exists()) {
  553
+			if($record->hasExtension('Hierarchy')) {
  554
+				$ancestors = $record->getAncestors();
  555
+				$ancestors = new ArrayList(array_reverse($ancestors->toArray()));
  556
+				$ancestors->push($record);
  557
+				foreach($ancestors as $ancestor) {
  558
+					$items->push(new ArrayData(array(
  559
+						'Title' => $ancestor->Title,
  560
+						'Link' => ($unlinked) ? false : Controller::join_links($this->Link('show'), $ancestor->ID)
  561
+					)));		
  562
+				}
  563
+			} else {
  564
+				$items->push(new ArrayData(array(
  565
+					'Title' => $record->Title,
  566
+					'Link' => ($unlinked) ? false : Controller::join_links($this->Link('show'), $record->ID)
  567
+				)));	
  568
+			}
  569
+		}
  570
+
  571
+		return $items;
  572
+	}
506 573
 	
507 574
 	/**
508 575
 	 * @return String HTML
@@ -522,10 +589,22 @@ public function SiteTreeAsUL() {
522 589
 	 * @return String Nested unordered list with links to each page
523 590
 	 */
524 591
 	function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, $filterFunction = null, $minNodeCount = 30) {
  592
+		// Filter criteria
  593
+		$params = $this->request->getVar('q');
  594
+		if(isset($params['FilterClass']) && $filterClass = $params['FilterClass']){
  595
+			if(!is_subclass_of($filterClass, 'CMSSiteTreeFilter')) {
  596
+				throw new Exception(sprintf('Invalid filter class passed: %s', $filterClass));
  597
+			}
  598
+			$filter = new $filterClass($params);
  599
+		} else {
  600
+			$filter = null;
  601
+		}
  602
+
525 603
 		// Default childrenMethod and numChildrenMethod
526  
-		if (!$childrenMethod) $childrenMethod = 'AllChildrenIncludingDeleted';
527  
-		if (!$numChildrenMethod) $numChildrenMethod = 'numChildren';
528  
-		
  604
+		if(!$childrenMethod) $childrenMethod = ($filter && $filter->getChildrenMethod()) ? $filter->getChildrenMethod() : 'AllChildrenIncludingDeleted';
  605
+		if(!$numChildrenMethod) $numChildrenMethod = 'numChildren';
  606
+		if(!$filterFunction) $filterFunction = ($filter) ? array($filter, 'isPageIncluded') : null;
  607
+
529 608
 		// Get the tree root
530 609
 		$record = ($rootID) ? $this->getRecord($rootID) : null;
531 610
 		$obj = $record ? $record : singleton($className);
@@ -544,19 +623,22 @@ function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $num
544 623
 		}
545 624
 
546 625
 		// getChildrenAsUL is a flexible and complex way of traversing the tree
547  
-		$titleEval = '
548  
-			"<li id=\"record-$child->ID\" data-id=\"$child->ID\" class=\"" . $child->CMSTreeClasses($extraArg) . "\">" .
549  
-			"<ins class=\"jstree-icon\">&nbsp;</ins>" .
550  
-			"<a href=\"" . Controller::join_links($extraArg->Link("show"), $child->ID) . "\" title=\"' 
551  
-			. _t('LeftAndMain.PAGETYPE','Page type: ') 
552  
-			. '".$child->class."\" ><ins class=\"jstree-icon\">&nbsp;</ins><span class=\"text\">" . ($child->TreeTitle) . 
553  
-			"</span></a>"
554  
-		';
555  
-
  626
+		$controller = $this;
  627
+		$recordController = ($this->stat('tree_class') == 'SiteTree') ?  singleton('CMSPageEditController') : $this;
  628
+		$titleFn = function(&$child) use(&$controller, &$recordController) {
  629
+			$classes = $child->CMSTreeClasses();
  630
+			if($controller->isCurrentPage($child)) $classes .= " current";
  631
+			return "<li id=\"record-$child->ID\" data-id=\"$child->ID\" data-ssclass=\"$child->ClassName\" class=\"" . $classes . "\">" .
  632
+				"<ins class=\"jstree-icon\">&nbsp;</ins>" .
  633
+				"<a href=\"" . Controller::join_links($recordController->Link("show"), $child->ID) . "\" title=\"" .
  634
+				_t('LeftAndMain.PAGETYPE','Page type: ') .
  635
+				"$child->class\" ><ins class=\"jstree-icon\">&nbsp;</ins><span class=\"text\">" . ($child->TreeTitle).
  636
+				"</span></a>";
  637
+		};
556 638
 		$html = $obj->getChildrenAsUL(
557  
-			"", 
558  
-			$titleEval,
559  
-			$this, 
  639
+			"",
  640
+			$titleFn,
  641
+			singleton('CMSPagesController'),
560 642
 			true, 
561 643
 			$childrenMethod,
562 644
 			$numChildrenMethod,
@@ -577,7 +659,7 @@ function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $num
577 659
 				$treeTitle = '...';
578 660
 			}
579 661
 			
580  
-			$html = "<ul><li id=\"record-0\" data-id=\"0\" class=\"Root nodelete\"><a href=\"$rootLink\"><strong>$treeTitle</strong></a>"
  662
+			$html = "<ul><li id=\"record-0\" data-id=\"0\" class=\"Root nodelete\"><strong>$treeTitle</strong>"
581 663
 				. $html . "</li></ul>";
582 664
 		}
583 665
 
@@ -589,22 +671,12 @@ function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $num
589 671
 	 * If ID = 0, then get the whole tree.
590 672
 	 */
591 673
 	public function getsubtree($request) {
592  
-		if($filterClass = $request->requestVar('FilterClass')) {
593  
-			if(!is_subclass_of($filterClass, 'CMSSiteTreeFilter')) {
594  
-				throw new Exception(sprintf('Invalid filter class passed: %s', $filterClass));
595  
-			}
596  
-
597  
-			$filter = new $filterClass($request->requestVars());
598  
-		} else {
599  
-			$filter = null;
600  
-		}
601  
-		
602 674
 		$html = $this->getSiteTreeFor(
603 675
 			$this->stat('tree_class'), 
604 676
 			$request->getVar('ID'), 
605  
-			($filter) ? $filter->getChildrenMethod() : null, 
  677
+			null, 
606 678
 			null,
607  
-			($filter) ? array($filter, 'isPageIncluded') : null, 
  679
+			null, 
608 680
 			$request->getVar('minNodeCount')
609 681
 		);
610 682
 
@@ -636,13 +708,10 @@ public function save($data, $form) {
636 708
 		$form->saveInto($record, true);
637 709
 		$record->write();
638 710
 		$this->extend('onAfterSave', $record);
639  
-
640  
-		$this->response->addHeader('X-Status', _t('LeftAndMain.SAVEDUP'));
641  
-		
642  
-		// write process might've changed the record, so we reload before returning
643  
-		$form = $this->getEditForm($record->ID);
  711
+		$this->setCurrentPageID($record->ID);
644 712
 		
645  
-		return $form->forTemplate();
  713
+		$this->response->addHeader('X-Status', _t('LeftAndMain.SAVEDUP'));
  714
+		return $this->getResponseNegotiator()->respond($this->request);
646 715
 	}
647 716
 	
648 717
 	public function delete($data, $form) {
@@ -653,12 +722,12 @@ public function delete($data, $form) {
653 722
 		if(!$record || !$record->ID) throw new HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
654 723
 		
655 724
 		$record->delete();
656  
-		
657  
-		if($this->isAjax()) {
658  
-			return $this->EmptyForm()->forTemplate();
659  
-		} else {
660  
-			$this->redirectBack();
661  
-		}
  725
+
  726
+		$this->response->addHeader('X-Status', _t('LeftAndMain.SAVEDUP'));
  727
+		return $this->getResponseNegotiator()->respond(
  728
+			$this->request, 
  729
+			array('currentform' => array($this, 'EmptyForm'))
  730
+		);
662 731
 	}
663 732
 
664 733
 	/**
@@ -830,16 +899,24 @@ public function getEditForm($id = null, $fields = null) {
830 899
 				$actions = $record->getCMSActions();
831 900
 				// add default actions if none are defined
832 901
 				if(!$actions || !$actions->Count()) {
833  
-					if($record->hasMethod('canDelete') && $record->canDelete()) {
834  
-						$actions->push($deleteAction = new FormAction('delete',_t('ModelAdmin.DELETE','Delete')));
835  
-						$deleteAction->addExtraClass('ss-ui-action-destructive');
836  
-					}
837 902
 					if($record->hasMethod('canEdit') && $record->canEdit()) {
838  
-						$actions->push($saveAction = new FormAction('save',_t('CMSMain.SAVE','Save')));
839  
-						$saveAction->addExtraClass('ss-ui-action-constructive');
  903
+						$actions->push(
  904
+							FormAction::create('save',_t('CMSMain.SAVE','Save'))
  905
+								->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept')
  906
+						);
  907
+					}
  908
+					if($record->hasMethod('canDelete') && $record->canDelete()) {
  909
+						$actions->push(
  910
+							FormAction::create('delete',_t('ModelAdmin.DELETE','Delete'))
  911
+								->addExtraClass('ss-ui-action-destructive')
  912
+						);
840 913
 					}
841 914
 				}
842 915
 			}
  916
+
  917
+			// Use <button> to allow full jQuery UI styling
  918
+			$actionsFlattened = $actions->dataFields();
  919
+			if($actionsFlattened) foreach($actionsFlattened as $action) $action->setUseButtonTag(true);
843 920
 			
844 921
 			$form = new Form($this, "EditForm", $fields, $actions);
845 922
 			$form->addExtraClass('cms-edit-form');
@@ -863,7 +940,6 @@ public function getEditForm($id = null, $fields = null) {
863 940
 				// The clientside (mainly LeftAndMain*.js) rely on ajax responses
864 941
 				// which can be evaluated as javascript, hence we need
865 942
 				// to override any global changes to the validation handler.