silverstripe /
silverstripe-framework
| 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( |
||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
| 34 | '' => 'index', |
||
| 35 | 'build/defaults' => 'buildDefaults', |
||
| 36 | 'generatesecuretoken' => 'generatesecuretoken', |
||
| 37 | '$Action' => 'runRegisteredController', |
||
| 38 | ); |
||
| 39 | |||
| 40 | private static $allowed_actions = array( |
||
|
0 ignored issues
–
show
|
|||
| 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; |
||
|
0 ignored issues
–
show
|
|||
| 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; |
||
|
0 ignored issues
–
show
|
|||
| 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 |