1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SilverStripe\Dev; |
4
|
|
|
|
5
|
|
|
use SilverStripe\Control\HTTPResponse_Exception; |
6
|
|
|
use SilverStripe\Core\Config\Config; |
7
|
|
|
use SilverStripe\Core\Injector\Injector; |
8
|
|
|
use SilverStripe\Control\Director; |
9
|
|
|
use SilverStripe\Control\HTTPRequest; |
10
|
|
|
use SilverStripe\Control\HTTPResponse; |
11
|
|
|
use SilverStripe\Control\Controller; |
12
|
|
|
use SilverStripe\Security\RandomGenerator; |
13
|
|
|
use SilverStripe\Versioned\Versioned; |
14
|
|
|
use SilverStripe\ORM\DatabaseAdmin; |
15
|
|
|
use SilverStripe\Security\Permission; |
16
|
|
|
use SilverStripe\Security\Security; |
17
|
|
|
use Exception; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Base class for development tools. |
21
|
|
|
* |
22
|
|
|
* Configured in framework/_config/dev.yml, with the config key registeredControllers being |
23
|
|
|
* used to generate the list of links for /dev. |
24
|
|
|
* |
25
|
|
|
* @todo documentation for how to add new unit tests and tasks |
26
|
|
|
* @todo do we need buildDefaults and generatesecuretoken? if so, register in the list |
27
|
|
|
* @todo cleanup errors() it's not even an allowed action, so can go |
28
|
|
|
* @todo cleanup index() html building |
29
|
|
|
*/ |
30
|
|
|
class DevelopmentAdmin extends Controller |
31
|
|
|
{ |
32
|
|
|
|
33
|
|
|
private static $url_handlers = array( |
|
|
|
|
34
|
|
|
'' => 'index', |
35
|
|
|
'build/defaults' => 'buildDefaults', |
36
|
|
|
'generatesecuretoken' => 'generatesecuretoken', |
37
|
|
|
'$Action' => 'runRegisteredController', |
38
|
|
|
); |
39
|
|
|
|
40
|
|
|
private static $allowed_actions = array( |
|
|
|
|
41
|
|
|
'index', |
42
|
|
|
'buildDefaults', |
43
|
|
|
'runRegisteredController', |
44
|
|
|
'generatesecuretoken', |
45
|
|
|
); |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Assume that CLI equals admin permissions |
49
|
|
|
* If set to false, normal permission model will apply even in CLI mode |
50
|
|
|
* Applies to all development admin tasks (E.g. TaskRunner, DatabaseAdmin) |
51
|
|
|
* |
52
|
|
|
* @config |
53
|
|
|
* @var bool |
54
|
|
|
*/ |
55
|
|
|
private static $allow_all_cli = true; |
|
|
|
|
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Deny all non-cli requests (browser based ones) to dev admin |
59
|
|
|
* |
60
|
|
|
* @config |
61
|
|
|
* @var bool |
62
|
|
|
*/ |
63
|
|
|
private static $deny_non_cli = false; |
|
|
|
|
64
|
|
|
|
65
|
|
|
protected function init() |
66
|
|
|
{ |
67
|
|
|
parent::init(); |
68
|
|
|
|
69
|
|
|
if (static::config()->get('deny_non_cli') && !Director::is_cli()) { |
70
|
|
|
return $this->httpError(404); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
// Special case for dev/build: Defer permission checks to DatabaseAdmin->init() (see #4957) |
74
|
|
|
$requestedDevBuild = (stripos($this->getRequest()->getURL(), 'dev/build') === 0) |
75
|
|
|
&& (stripos($this->getRequest()->getURL(), 'dev/build/defaults') === false); |
76
|
|
|
|
77
|
|
|
// We allow access to this controller regardless of live-status or ADMIN permission only |
78
|
|
|
// if on CLI. Access to this controller is always allowed in "dev-mode", or of the user is ADMIN. |
79
|
|
|
$allowAllCLI = static::config()->get('allow_all_cli'); |
80
|
|
|
$canAccess = ( |
81
|
|
|
$requestedDevBuild |
82
|
|
|
|| Director::isDev() |
83
|
|
|
|| (Director::is_cli() && $allowAllCLI) |
84
|
|
|
// Its important that we don't run this check if dev/build was requested |
85
|
|
|
|| Permission::check("ADMIN") |
86
|
|
|
); |
87
|
|
|
if (!$canAccess) { |
88
|
|
|
Security::permissionFailure($this); |
89
|
|
|
return; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
// Backwards compat: Default to "draft" stage, which is important |
93
|
|
|
// for tasks like dev/build which call DataObject->requireDefaultRecords(), |
94
|
|
|
// but also for other administrative tasks which have assumptions about the default stage. |
95
|
|
|
if (class_exists(Versioned::class)) { |
96
|
|
|
Versioned::set_stage(Versioned::DRAFT); |
97
|
|
|
} |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
public function index() |
101
|
|
|
{ |
102
|
|
|
// Web mode |
103
|
|
|
if (!Director::is_cli()) { |
104
|
|
|
$renderer = DebugView::create(); |
105
|
|
|
echo $renderer->renderHeader(); |
106
|
|
|
echo $renderer->renderInfo("SilverStripe Development Tools", Director::absoluteBaseURL()); |
107
|
|
|
$base = Director::baseURL(); |
108
|
|
|
|
109
|
|
|
echo '<div class="options"><ul>'; |
110
|
|
|
$evenOdd = "odd"; |
111
|
|
|
foreach (self::get_links() as $action => $description) { |
112
|
|
|
echo "<li class=\"$evenOdd\"><a href=\"{$base}dev/$action\"><b>/dev/$action:</b>" |
113
|
|
|
. " $description</a></li>\n"; |
114
|
|
|
$evenOdd = ($evenOdd == "odd") ? "even" : "odd"; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
echo $renderer->renderFooter(); |
118
|
|
|
|
119
|
|
|
// CLI mode |
120
|
|
|
} else { |
121
|
|
|
echo "SILVERSTRIPE DEVELOPMENT TOOLS\n--------------------------\n\n"; |
122
|
|
|
echo "You can execute any of the following commands:\n\n"; |
123
|
|
|
foreach (self::get_links() as $action => $description) { |
124
|
|
|
echo " sake dev/$action: $description\n"; |
125
|
|
|
} |
126
|
|
|
echo "\n\n"; |
127
|
|
|
} |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* @param HTTPRequest $request |
132
|
|
|
* @return Controller |
133
|
|
|
* @throws Exception |
134
|
|
|
* @throws HTTPResponse_Exception |
135
|
|
|
*/ |
136
|
|
|
public function runRegisteredController(HTTPRequest $request) |
137
|
|
|
{ |
138
|
|
|
$controllerClass = null; |
139
|
|
|
|
140
|
|
|
$baseUrlPart = $request->param('Action'); |
141
|
|
|
$reg = Config::inst()->get(__CLASS__, 'registered_controllers'); |
142
|
|
|
if (isset($reg[$baseUrlPart])) { |
143
|
|
|
$controllerClass = $reg[$baseUrlPart]['controller']; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
if ($controllerClass && class_exists($controllerClass)) { |
147
|
|
|
return $controllerClass::create(); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
$msg = 'Error: no controller registered in ' . __CLASS__ . ' for: ' . $request->param('Action'); |
151
|
|
|
if (Director::is_cli()) { |
152
|
|
|
// in CLI we cant use httpError because of a bug with stuff being in the output already, |
153
|
|
|
// see DevAdminControllerTest |
154
|
|
|
throw new Exception($msg); |
155
|
|
|
} else { |
156
|
|
|
$this->httpError(404, $msg); |
157
|
|
|
} |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
|
161
|
|
|
|
162
|
|
|
|
163
|
|
|
/* |
164
|
|
|
* Internal methods |
165
|
|
|
*/ |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @return array of url => description |
169
|
|
|
*/ |
170
|
|
|
protected static function get_links() |
171
|
|
|
{ |
172
|
|
|
$links = []; |
173
|
|
|
|
174
|
|
|
$reg = Config::inst()->get(__CLASS__, 'registered_controllers'); |
175
|
|
|
foreach ($reg as $registeredController) { |
176
|
|
|
if (isset($registeredController['links'])) { |
177
|
|
|
foreach ($registeredController['links'] as $url => $desc) { |
178
|
|
|
$links[$url] = $desc; |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
return $links; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
protected function getRegisteredController($baseUrlPart) |
186
|
|
|
{ |
187
|
|
|
$reg = Config::inst()->get(__CLASS__, 'registered_controllers'); |
188
|
|
|
|
189
|
|
|
if (isset($reg[$baseUrlPart])) { |
190
|
|
|
$controllerClass = $reg[$baseUrlPart]['controller']; |
191
|
|
|
return $controllerClass; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
return null; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
|
198
|
|
|
|
199
|
|
|
|
200
|
|
|
/* |
201
|
|
|
* Unregistered (hidden) actions |
202
|
|
|
*/ |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Build the default data, calling requireDefaultRecords on all |
206
|
|
|
* DataObject classes |
207
|
|
|
* Should match the $url_handlers rule: |
208
|
|
|
* 'build/defaults' => 'buildDefaults', |
209
|
|
|
*/ |
210
|
|
|
public function buildDefaults() |
211
|
|
|
{ |
212
|
|
|
$da = DatabaseAdmin::create(); |
213
|
|
|
|
214
|
|
|
$renderer = null; |
215
|
|
|
if (!Director::is_cli()) { |
216
|
|
|
$renderer = DebugView::create(); |
217
|
|
|
echo $renderer->renderHeader(); |
218
|
|
|
echo $renderer->renderInfo("Defaults Builder", Director::absoluteBaseURL()); |
219
|
|
|
echo "<div class=\"build\">"; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
$da->buildDefaults(); |
223
|
|
|
|
224
|
|
|
if (!Director::is_cli()) { |
225
|
|
|
echo "</div>"; |
226
|
|
|
echo $renderer->renderFooter(); |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Generate a secure token which can be used as a crypto key. |
232
|
|
|
* Returns the token and suggests PHP configuration to set it. |
233
|
|
|
* |
234
|
|
|
* @return HTTPResponse |
235
|
|
|
*/ |
236
|
|
|
public function generatesecuretoken() |
237
|
|
|
{ |
238
|
|
|
$generator = Injector::inst()->create(RandomGenerator::class); |
239
|
|
|
$token = $generator->randomToken('sha1'); |
240
|
|
|
$body = <<<TXT |
241
|
|
|
Generated new token. Please add the following code to your YAML configuration: |
242
|
|
|
|
243
|
|
|
Security: |
244
|
|
|
token: $token |
245
|
|
|
|
246
|
|
|
TXT; |
247
|
|
|
$response = HTTPResponse::create($body); |
248
|
|
|
return $response->addHeader('Content-Type', 'text/plain'); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
public function errors() |
252
|
|
|
{ |
253
|
|
|
$this->redirect("Debug_"); |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
|