{$html}\n"; 166 | } 167 | 168 | /** 169 | * Formats the caller of a method 170 | * 171 | * Improve method by creating the ide link 172 | * 173 | * @param array $caller 174 | * @return string 175 | */ 176 | protected function formatCaller($caller) 177 | { 178 | $return = self::makeIdeLink($caller['file'], $caller['line']); 179 | if (!empty($caller['class']) && !empty($caller['function'])) { 180 | $return .= " - {$caller['class']}::{$caller['function']}()"; 181 | } 182 | return $return; 183 | } 184 | 185 | /** 186 | * Render an error. 187 | * 188 | * @param string $httpRequest the kind of request 189 | * @param int $errno Codenumber of the error 190 | * @param string $errstr The error message 191 | * @param string $errfile The name of the soruce code file where the error occurred 192 | * @param int $errline The line number on which the error occured 193 | * @return string 194 | */ 195 | public function renderError($httpRequest, $errno, $errstr, $errfile, $errline) 196 | { 197 | $errorType = isset(self::$error_types[$errno]) ? self::$error_types[$errno] : self::$unknown_error; 198 | $httpRequestEnt = htmlentities($httpRequest, ENT_COMPAT, 'UTF-8'); 199 | if (ini_get('html_errors')) { 200 | $errstr = strip_tags($errstr); 201 | } else { 202 | $errstr = Convert::raw2xml($errstr); 203 | } 204 | 205 | $infos = self::makeIdeLink($errfile, $errline); 206 | 207 | $output = '
$infos
"; 211 | $output .= '" . get_class($exception) . " in $infos
"; 226 | $message = $exception->getMessage(); 227 | if ($exception instanceof DatabaseException) { 228 | $sql = $exception->getSQL(); 229 | // Some database errors don't have sql 230 | if ($sql) { 231 | $parameters = $exception->getParameters(); 232 | $sql = DB::inline_parameters($sql, $parameters); 233 | $formattedSQL = DatabaseHelper::formatSQL($sql); 234 | $message .= "" . $message . "
"; 238 | $output .= '$message"; 220 | } else { 221 | echo "
tag below the field 99 | */ 100 | public function getHTMLFragments($gridField) 101 | { 102 | $title = $this->buttonTitle ? $this->buttonTitle : _t( 103 | 'TableListField.FASTEXPORT', 104 | 'Fast Export' 105 | ); 106 | 107 | $name = $this->getActionName(); 108 | 109 | $button = new GridField_FormAction( 110 | $gridField, 111 | $name, 112 | $title, 113 | $name, 114 | null 115 | ); 116 | $button->addExtraClass('no-ajax action_export'); 117 | $button->setForm($gridField->getForm()); 118 | 119 | return array( 120 | $this->targetFragment => '
', 121 | ); 122 | } 123 | 124 | /** 125 | * export is an action button 126 | */ 127 | public function getActions($gridField) 128 | { 129 | return array($this->getActionName()); 130 | } 131 | 132 | public function handleAction( 133 | GridField $gridField, 134 | $actionName, 135 | $arguments, 136 | $data 137 | ) { 138 | if (in_array($actionName, $this->getActions($gridField))) { 139 | return $this->handleExport($gridField); 140 | } 141 | } 142 | 143 | /** 144 | * it is also a URL 145 | */ 146 | public function getURLHandlers($gridField) 147 | { 148 | return array($this->getActionName() => 'handleExport'); 149 | } 150 | 151 | /** 152 | * Handle the export, for both the action button and the URL 153 | */ 154 | public function handleExport($gridField, $request = null) 155 | { 156 | $now = Date("Ymd_Hi"); 157 | 158 | if ($fileData = $this->generateExportFileData($gridField)) { 159 | $name = $this->exportName; 160 | $ext = 'csv'; 161 | $fileName = "$name-$now.$ext"; 162 | 163 | return HTTPRequest::send_file($fileData, $fileName, 'text/csv'); 164 | } 165 | } 166 | 167 | public static function allFieldsForClass($class) 168 | { 169 | $dataClasses = ClassInfo::dataClassesFor($class); 170 | $fields = array(); 171 | foreach ($dataClasses as $dataClass) { 172 | $databaseFields = DataObject::getSchema()->databaseFields($dataClass); 173 | 174 | $dataFields = []; 175 | foreach ($databaseFields as $name => $type) { 176 | if ($type == 'Text' || $type == 'HTMLText') { 177 | continue; 178 | } 179 | $dataFields[] = $name; 180 | } 181 | $fields = array_merge( 182 | $fields, 183 | $dataFields 184 | ); 185 | } 186 | return array_combine($fields, $fields); 187 | } 188 | 189 | public static function exportFieldsForClass($class) 190 | { 191 | $singl = singleton($class); 192 | if ($singl->hasMethod('exportedFields')) { 193 | return $singl->exportedFields(); 194 | } 195 | $exportedFields = Config::inst()->get($class, 'exported_fields'); 196 | if (!$exportedFields) { 197 | $exportedFields = array_keys(self::allFieldsForClass($class)); 198 | } 199 | $unexportedFields = Config::inst()->get($class, 'unexported_fields'); 200 | if ($unexportedFields) { 201 | $exportedFields = array_diff($exportedFields, $unexportedFields); 202 | } 203 | return array_combine($exportedFields, $exportedFields); 204 | } 205 | 206 | /** 207 | * Generate export fields 208 | * 209 | * @param GridField $gridField 210 | * @return string 211 | */ 212 | public function generateExportFileData($gridField) 213 | { 214 | $class = $gridField->getModelClass(); 215 | $columns = ($this->exportColumns) ? $this->exportColumns : self::exportFieldsForClass($class); 216 | $fileData = ''; 217 | 218 | // If we don't have an associative array 219 | if (!ArrayLib::is_associative($columns)) { 220 | array_combine($columns, $columns); 221 | } 222 | 223 | $singl = singleton($class); 224 | 225 | $singular = $class ? $singl->i18n_singular_name() : ''; 226 | $plural = $class ? $singl->i18n_plural_name() : ''; 227 | 228 | $filter = new FileNameFilter; 229 | if ($this->exportName) { 230 | $this->exportName = $filter->filter($this->exportName); 231 | } else { 232 | $this->exportName = $filter->filter('fastexport-' . $plural); 233 | } 234 | 235 | $fileData = ''; 236 | $separator = $this->csvSeparator; 237 | 238 | $class = $gridField->getModelClass(); 239 | $singl = singleton($class); 240 | $baseTable = $singl->baseTable(); 241 | 242 | $stream = fopen('data://text/plain,' . "", 'w+'); 243 | 244 | // Filter columns 245 | $sqlFields = []; 246 | $baseFields = ['ID', 'Created', 'LastEdited']; 247 | 248 | $joins = []; 249 | $isSubsite = false; 250 | $map = []; 251 | if ($singl->hasMethod('fastExportMap')) { 252 | $map = $singl->fastExportMap(); 253 | } 254 | foreach ($columns as $columnSource => $columnHeader) { 255 | // Allow mapping methods to plain fields 256 | if ($map && isset($map[$columnSource])) { 257 | $columnSource = $map[$columnSource]; 258 | } 259 | if ($columnSource == 'SubsiteID') { 260 | $isSubsite = true; 261 | } 262 | if (in_array($columnSource, $baseFields)) { 263 | $sqlFields[] = $baseTable . '.' . $columnSource; 264 | continue; 265 | } 266 | // Naive join support 267 | if (strpos($columnSource, '.') !== false) { 268 | $parts = explode('.', $columnSource); 269 | 270 | $joinSingl = singleton($parts[0]); 271 | $joinBaseTable = $joinSingl->baseTable(); 272 | 273 | if (!isset($joins[$joinBaseTable])) { 274 | $joins[$joinBaseTable] = []; 275 | } 276 | $joins[$joinBaseTable][] = $parts[1]; 277 | 278 | $sqlFields[] = $joinBaseTable . '.' . $parts[1]; 279 | continue; 280 | } 281 | $fieldTable = ClassInfo::table_for_object_field($class, $columnSource); 282 | if ($fieldTable != $baseTable || !$fieldTable) { 283 | unset($columns[$columnSource]); 284 | } else { 285 | $sqlFields[] = $fieldTable . '.' . $columnSource; 286 | } 287 | } 288 | 289 | if ($this->hasHeader) { 290 | $headers = array(); 291 | 292 | // determine the headers. If a field is callable (e.g. anonymous function) then use the 293 | // source name as the header instead 294 | foreach ($columns as $columnSource => $columnHeader) { 295 | $headers[] = (!is_string($columnHeader) && is_callable($columnHeader)) 296 | ? $columnSource : $columnHeader; 297 | } 298 | 299 | $row = array_values($headers); 300 | // fputcsv($stream, $row, $separator); 301 | 302 | // force quotes 303 | fputs($stream, implode(",", array_map("self::encodeFunc", $row)) . "\n"); 304 | } 305 | 306 | if (empty($sqlFields)) { 307 | $sqlFields = ['ID', 'Created', 'LastEdited']; 308 | } 309 | 310 | $where = []; 311 | $sql = 'SELECT ' . implode(',', $sqlFields) . ' FROM ' . $baseTable; 312 | foreach ($joins as $joinTable => $joinFields) { 313 | $foreignKey = $joinTable . 'ID'; 314 | $sql .= ' LEFT JOIN ' . $joinTable . ' ON ' . $joinTable . '.ID = ' . $baseTable . '.' . $foreignKey; 315 | } 316 | // Basic subsite support 317 | if ($isSubsite && class_exists('Subsite') && Subsite::currentSubsiteID()) { 318 | $where[] = $baseTable . '.SubsiteID = ' . Subsite::currentSubsiteID(); 319 | } 320 | 321 | $singl->extend('updateFastExport', $sql, $where); 322 | 323 | // Basic where clause 324 | if (!empty($where)) { 325 | $sql .= ' WHERE ' . implode(' AND ', $where); 326 | } 327 | 328 | $query = DB::query($sql); 329 | 330 | foreach ($query as $row) { 331 | // fputcsv($stream, $row, $separator); 332 | 333 | // force quotes 334 | fputs($stream, implode(",", array_map("self::encodeFunc", $row)) . "\n"); 335 | } 336 | 337 | rewind($stream); 338 | $fileData = stream_get_contents($stream); 339 | fclose($stream); 340 | 341 | return $fileData; 342 | } 343 | 344 | public static function encodeFunc($value) 345 | { 346 | ///remove any ESCAPED double quotes within string. 347 | $value = str_replace('\\"', '"', $value); 348 | //then force escape these same double quotes And Any UNESCAPED Ones. 349 | $value = str_replace('"', '\"', $value); 350 | //force wrap value in quotes and return 351 | return '"' . $value . '"'; 352 | } 353 | 354 | /** 355 | * @return array 356 | */ 357 | public function getExportColumns() 358 | { 359 | return $this->exportColumns; 360 | } 361 | 362 | /** 363 | * @param array 364 | */ 365 | public function setExportColumns($cols) 366 | { 367 | $this->exportColumns = $cols; 368 | return $this; 369 | } 370 | 371 | /** 372 | * @return boolean 373 | */ 374 | public function getHasHeader() 375 | { 376 | return $this->hasHeader; 377 | } 378 | 379 | /** 380 | * @param boolean 381 | */ 382 | public function setHasHeader($bool) 383 | { 384 | $this->hasHeader = $bool; 385 | return $this; 386 | } 387 | 388 | /** 389 | * @return string 390 | */ 391 | public function getExportName() 392 | { 393 | return $this->exportName; 394 | } 395 | 396 | /** 397 | * @param string $exportName 398 | * @return $this 399 | */ 400 | public function setExportName($exportName) 401 | { 402 | $this->exportName = $exportName; 403 | return $this; 404 | } 405 | 406 | /** 407 | * @return string 408 | */ 409 | public function getButtonTitle() 410 | { 411 | return $this->buttonTitle; 412 | } 413 | 414 | /** 415 | * @param string $buttonTitle 416 | * @return $this 417 | */ 418 | public function setButtonTitle($buttonTitle) 419 | { 420 | $this->buttonTitle = $buttonTitle; 421 | return $this; 422 | } 423 | 424 | /** 425 | * @return string 426 | */ 427 | public function getCsvSeparator() 428 | { 429 | return $this->csvSeparator; 430 | } 431 | 432 | /** 433 | * @param string 434 | */ 435 | public function setCsvSeparator($separator) 436 | { 437 | $this->csvSeparator = $separator; 438 | return $this; 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /src/Extensions/DevBuildExtension.php: -------------------------------------------------------------------------------- 1 | owner; 46 | } 47 | 48 | /** 49 | * @return HTTPRequest 50 | */ 51 | public function getRequest() 52 | { 53 | return $this->getExtensionOwner()->getRequest(); 54 | } 55 | 56 | /** 57 | * @return void 58 | */ 59 | public function beforeCallActionHandler() 60 | { 61 | $this->currentSubsite = SubsiteHelper::currentSubsiteID(); 62 | 63 | $annotate = $this->getRequest()->getVar('annotate'); 64 | if ($annotate) { 65 | \SilverLeague\IDEAnnotator\DataObjectAnnotator::config()->enabled = true; 66 | } else { 67 | \SilverLeague\IDEAnnotator\DataObjectAnnotator::config()->enabled = false; 68 | } 69 | 70 | $renameColumns = $this->getRequest()->getVar('fixTableCase'); 71 | if ($renameColumns) { 72 | $this->displayMessage("Fixing tables case
Tables fixed!
Renaming columns
Columns renamed!
Truncating SiteTree
SiteTree truncated!
Provisioning locales
Locales provisioned!
" . self::sentences(3, 7) . "
"; 482 | } 483 | return implode("\n", $res); 484 | } 485 | 486 | /** 487 | * Get a date between two dates 488 | * 489 | * @param string $num 490 | * @param string $num2 491 | * @param string $format 492 | * @return string 493 | */ 494 | public static function date($num, $num2, $format = 'Y-m-d H:i:s') 495 | { 496 | if (is_string($num)) { 497 | $num = strtotime($num); 498 | } 499 | if (is_string($num2)) { 500 | $num2 = strtotime($num2); 501 | } 502 | $rand = rand($num, $num2); 503 | return date($format, $rand); 504 | } 505 | 506 | /** 507 | * Male or female 508 | * 509 | * @return string 510 | */ 511 | public static function male_or_female() 512 | { 513 | return self::pick(['male', 'female']); 514 | } 515 | 516 | /** 517 | * Randomly pick in an array 518 | * 519 | * @param array $arr 520 | * @return array 521 | */ 522 | public static function pick(array $arr) 523 | { 524 | return $arr[array_rand($arr)]; 525 | } 526 | 527 | /** 528 | * Get a random country 529 | * 530 | * @return string 531 | */ 532 | public static function country() 533 | { 534 | $countries = array_values(CountriesList::get()); 535 | return $countries[array_rand($countries)]; 536 | } 537 | 538 | /** 539 | * Get a random country code 540 | * 541 | * @return string 542 | */ 543 | public static function countryCode() 544 | { 545 | $countries = array_keys(CountriesList::get()); 546 | return $countries[array_rand($countries)]; 547 | } 548 | } 549 | -------------------------------------------------------------------------------- /src/Helpers/DevUtils.php: -------------------------------------------------------------------------------- 1 | getProperty($prop); 20 | $refProperty->setAccessible(true); 21 | $refProperty->setValue($obj, $val); 22 | } 23 | 24 | /** 25 | * @param object $obj 26 | * @param string $prop 27 | * @param callable $cb 28 | * @return void 29 | */ 30 | public static function updatePropCb(object $obj, string $prop, callable $cb): void 31 | { 32 | $refObject = new ReflectionObject($obj); 33 | $refProperty = $refObject->getProperty($prop); 34 | $refProperty->setAccessible(true); 35 | $refProperty->setValue($obj, $cb($refProperty->getValue($obj))); 36 | } 37 | 38 | /** 39 | * @param object $obj 40 | * @param string $prop 41 | * @return mixed 42 | */ 43 | public static function getProp(object $obj, string $prop) 44 | { 45 | $refObject = new ReflectionObject($obj); 46 | $refProperty = $refObject->getProperty($prop); 47 | $refProperty->setAccessible(true); 48 | return $refProperty->getValue($obj); 49 | } 50 | 51 | /** 52 | * @param string $class 53 | * @param string $prop 54 | * @return mixed 55 | */ 56 | public static function getStaticProp(string $class, string $prop) 57 | { 58 | $refClass = new ReflectionClass($class); 59 | return $refClass->getStaticPropertyValue($prop); 60 | } 61 | 62 | /** 63 | * @param string $class 64 | * @param string $prop 65 | * @param mixed $val 66 | * @return void 67 | */ 68 | public static function updateStaticProp(string $class, string $prop, $val) 69 | { 70 | $refClass = new ReflectionClass($class); 71 | $refClass->setStaticPropertyValue($prop, $val); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Helpers/DuplicateMembersMerger.php: -------------------------------------------------------------------------------- 1 | byID($r->ID); 40 | } 41 | if (!$latest) { 42 | $latest = $r; 43 | } else { 44 | if (strtotime($r->LastEdited) > strtotime($latest->LastEdited)) { 45 | $latest = $r; 46 | } 47 | } 48 | if (!$oldest) { 49 | $oldest = $r; 50 | } else { 51 | if ($r->ID < $oldest->ID) { 52 | $oldest = $r->ID; 53 | } 54 | } 55 | 56 | $all[] = $r; 57 | } 58 | 59 | foreach ($all as $a) { 60 | if ($a->ID == $oldest->ID) { 61 | continue; 62 | } 63 | $all_but_oldest[] = $a; 64 | } 65 | 66 | foreach ($all as $a) { 67 | if ($a->ID == $latest->ID) { 68 | continue; 69 | } 70 | $all_but_latest[] = $a; 71 | } 72 | 73 | if (class_exists('Subsite')) { 74 | Subsite::$disable_subsite_filter = true; 75 | } 76 | 77 | Config::modify()->set(DataObject::class, 'validation_enabled', false); 78 | 79 | // Rewrite all relations so everything is pointing to oldest 80 | // For some reason, the code in merge fails to do this properly 81 | $tables = DataObject::getSchema()->getTableNames(); 82 | $objects = ClassInfo::subclassesFor('DataObject'); 83 | foreach ($objects as $o) { 84 | $config = $o::config(); 85 | if ($config->has_one) { 86 | foreach ($config->has_one as $name => $class) { 87 | if ($class == 'Member') { 88 | $table = ClassInfo::table_for_object_field( 89 | $o, 90 | $name . 'ID' 91 | ); 92 | if ($table && in_array(strtolower($table), $tables)) { 93 | foreach ($all_but_oldest as $a) { 94 | $sql = "UPDATE $table SET " . $name . 'ID = ' . $oldest->ID . ' WHERE ' . $name . 'ID = ' . $a->ID; 95 | DB::alteration_message($sql); 96 | DB::query($sql); 97 | } 98 | } 99 | } 100 | } 101 | } 102 | if ($config->has_many) { 103 | foreach ($config->has_many as $name => $class) { 104 | if ($class == 'Member') { 105 | $table = ClassInfo::table_for_object_field( 106 | $o, 107 | $name . 'ID' 108 | ); 109 | if ($table && in_array(strtolower($table), $tables)) { 110 | foreach ($all_but_oldest as $a) { 111 | $sql = "UPDATE $table SET " . $name . 'ID = ' . $oldest->ID . ' WHERE ' . $name . 'ID = ' . $a->ID; 112 | DB::alteration_message($sql); 113 | DB::query($sql); 114 | } 115 | } 116 | } 117 | } 118 | } 119 | if ($config->many_many) { 120 | foreach ($config->many_many as $name => $class) { 121 | if ($class == 'Member') { 122 | $table = ClassInfo::table_for_object_field( 123 | $o, 124 | $name . 'ID' 125 | ); 126 | if ($table && in_array(strtolower($table), $tables)) { 127 | foreach ($all_but_oldest as $a) { 128 | $sql = "UPDATE $table SET " . $name . 'ID = ' . $oldest->ID . ' WHERE ' . $name . 'ID = ' . $a->ID; 129 | DB::alteration_message($sql); 130 | DB::query($sql); 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | // Now, we update to oldest record with the latest info 139 | 140 | $orgOldest = $oldest; 141 | $oldest->merge($latest, 'right', false); 142 | foreach ($all_but_oldest as $a) { 143 | $a->delete(); 144 | } 145 | 146 | try { 147 | $oldest->write(); 148 | } catch (Exception $ex) { 149 | $orgOldest->write(); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Helpers/EmptySpaceFinder.php: -------------------------------------------------------------------------------- 1 | [\s]+$/'; 12 | 13 | public static function findSpacesInFiles($files) 14 | { 15 | echo ''; 16 | echo "Finding opened or closed tags ...\n\n"; 17 | 18 | $openings = []; 19 | $closings = []; 20 | foreach ($files as $file) { 21 | $content = file_get_contents($file); 22 | 23 | $matches = null; 24 | preg_match_all(self::REGEX_OPENING, $content, $matches); 25 | 26 | if (!empty($matches[0])) { 27 | $openings[] = $file; 28 | } 29 | 30 | $matches = null; 31 | preg_match_all(self::REGEX_CLOSING, $content, $matches); 32 | 33 | if (!empty($matches[0])) { 34 | $closings[] = $file; 35 | } 36 | } 37 | 38 | if (!empty($openings)) { 39 | echo "Files with opening tags that may need fixing\n"; 40 | foreach ($openings as $file) { 41 | echo "$file\n"; 42 | } 43 | echo "***\n"; 44 | } 45 | if (!empty($closings)) { 46 | echo "Files with closing tags that may need fixing\n"; 47 | foreach ($closings as $file) { 48 | echo "$file\n"; 49 | } 50 | echo "***\n"; 51 | } 52 | 53 | echo "\nDone!"; 54 | echo ''; 55 | die(); 56 | } 57 | 58 | public static function findSpacesInIncludedFiles() 59 | { 60 | $files = get_included_files(); 61 | self::findSpacesInFiles($files); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Helpers/EnvironmentChecker.php: -------------------------------------------------------------------------------- 1 | getRequest(); 19 | } 20 | return in_array($request->getIP(), ['127.0.0.1', '::1', '1']); 21 | } 22 | 23 | /** 24 | * Temp folder should always be there 25 | * 26 | * @return void 27 | */ 28 | public static function ensureTempFolderExists() 29 | { 30 | $tempFolder = Director::baseFolder() . '/silverstripe-cache'; 31 | if (!is_dir($tempFolder)) { 32 | mkdir($tempFolder, 0755); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Helpers/FileHelper.php: -------------------------------------------------------------------------------- 1 | 0 && $lines >= 0) { 44 | // Figure out how far back we should jump 45 | $seek = min(ftell($f), $buffer); 46 | // Do the jump (backwards, relative to where we are) 47 | fseek($f, -$seek, SEEK_CUR); 48 | // Read a chunk and prepend it to our output 49 | $output = ($chunk = fread($f, $seek)) . $output; 50 | // Jump back to where we started reading 51 | fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR); 52 | // Decrease our line counter 53 | $lines -= substr_count($chunk, "\n"); 54 | } 55 | // While we have too many lines 56 | // (Because of buffer size we might have read too many) 57 | while ($lines++ < 0) { 58 | // Find first newline and remove all text before that 59 | $output = substr($output, strpos($output, "\n") + 1); 60 | } 61 | // Close file and return 62 | fclose($f); 63 | return trim($output); 64 | } 65 | 66 | /** 67 | * Recursively remove a dir 68 | * 69 | * @param string $dir 70 | * @return bool 71 | */ 72 | public static function rmDir($dir) 73 | { 74 | if (!is_dir($dir)) { 75 | return false; 76 | } 77 | $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS); 78 | $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); 79 | foreach ($files as $file) { 80 | if ($file->isDir()) { 81 | rmdir($file->getRealPath()); 82 | } else { 83 | unlink($file->getRealPath()); 84 | } 85 | } 86 | return rmdir($dir); 87 | } 88 | 89 | /** 90 | * Check if a directory contains children 91 | * 92 | * @link https://stackoverflow.com/questions/6786014/php-fastest-way-to-find-if-directory-has-children 93 | * @param string $dir 94 | * @return bool 95 | */ 96 | public static function dirContainsChildren($dir) 97 | { 98 | $result = false; 99 | if ($dh = opendir($dir)) { 100 | while (!$result && ($file = readdir($dh)) !== false) { 101 | $result = $file !== "." && $file !== ".."; 102 | } 103 | closedir($dh); 104 | } 105 | return $result; 106 | } 107 | 108 | /** 109 | * @link https://www.digitalocean.com/community/questions/proper-permissions-for-web-server-s-directory 110 | * @param string $dir 111 | * @return bool 112 | */ 113 | public static function ensureDir($dir) 114 | { 115 | if (!is_dir($dir)) { 116 | return mkdir($dir, 0755, true); 117 | } 118 | return true; 119 | } 120 | 121 | /** 122 | * @param int $bytes 123 | * @param integer $decimals 124 | * @return string 125 | */ 126 | public static function humanFilesize($bytes, $decimals = 2) 127 | { 128 | if ($bytes < 1024) { 129 | return $bytes . ' B'; 130 | } 131 | $factor = floor(log($bytes, 1024)); 132 | return sprintf("%.{$decimals}f ", $bytes / pow(1024, $factor)) . ['B', 'KB', 'MB', 'GB', 'TB', 'PB'][$factor]; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Helpers/SubsiteHelper.php: -------------------------------------------------------------------------------- 1 | getSubsiteId(); 37 | } 38 | return 0; 39 | } 40 | 41 | /** 42 | * @return Subsite 43 | */ 44 | public static function currentSubsite() 45 | { 46 | $id = self::currentSubsiteID(); 47 | if (self::usesSubsite()) { 48 | return DataObject::get_by_id(Subsite::class, $id); 49 | } 50 | return false; 51 | } 52 | 53 | /** 54 | * Do we have the subsite module installed 55 | * TODO: check if it might be better to use module manifest instead? 56 | * 57 | * @return bool 58 | */ 59 | public static function usesSubsite() 60 | { 61 | return class_exists(SubsiteState::class); 62 | } 63 | 64 | /** 65 | * @return bool 66 | */ 67 | public static function subsiteFilterDisabled() 68 | { 69 | if (!self::usesSubsite()) { 70 | return true; 71 | } 72 | return Subsite::$disable_subsite_filter; 73 | } 74 | 75 | /** 76 | * Enable subsite filter and store previous state 77 | * 78 | * @return void 79 | */ 80 | public static function enableFilter() 81 | { 82 | if (!self::usesSubsite()) { 83 | return; 84 | } 85 | self::$previousState = Subsite::$disable_subsite_filter; 86 | Subsite::$disable_subsite_filter = false; 87 | } 88 | 89 | /** 90 | * Disable subsite filter and store previous state 91 | * 92 | * @return void 93 | */ 94 | public static function disableFilter() 95 | { 96 | if (!self::usesSubsite()) { 97 | return; 98 | } 99 | self::$previousState = Subsite::$disable_subsite_filter; 100 | Subsite::$disable_subsite_filter = true; 101 | } 102 | 103 | /** 104 | * Restore subsite filter based on previous set (set when called enableFilter or disableFilter) 105 | */ 106 | public static function restoreFilter() 107 | { 108 | if (!self::usesSubsite()) { 109 | return; 110 | } 111 | Subsite::$disable_subsite_filter = self::$previousState; 112 | } 113 | 114 | /** 115 | * @return int 116 | */ 117 | public static function SubsiteIDFromSession() 118 | { 119 | $session = Controller::curr()->getRequest()->getSession(); 120 | if ($session) { 121 | return $session->get('SubsiteID'); 122 | } 123 | return 0; 124 | } 125 | 126 | /** 127 | * Typically call this on PageController::init 128 | * This is due to InitStateMiddleware not using session in front end and not persisting get var parameters 129 | * 130 | * @param HTTPRequest $request 131 | * @return int 132 | */ 133 | public static function forceSubsiteFromRequest(HTTPRequest $request) 134 | { 135 | $subsiteID = $request->getVar('SubsiteID'); 136 | if ($subsiteID) { 137 | $request->getSession()->set('ForcedSubsiteID', $subsiteID); 138 | } else { 139 | $subsiteID = $request->getSession()->get('ForcedSubsiteID'); 140 | } 141 | if ($subsiteID) { 142 | self::changeSubsite($subsiteID, true); 143 | } 144 | return $subsiteID; 145 | } 146 | 147 | /** 148 | * @param string $ID 149 | * @param bool $flush 150 | * @return void 151 | */ 152 | public static function changeSubsite($ID, $flush = null) 153 | { 154 | if (!self::usesSubsite()) { 155 | return; 156 | } 157 | self::$previousSubsite = self::currentSubsiteID(); 158 | 159 | // Do this otherwise changeSubsite has no effect if false 160 | SubsiteState::singleton()->setUseSessions(true); 161 | Subsite::changeSubsite($ID); 162 | // This can help avoiding getting static objects like SiteConfig 163 | if ($flush !== null && $flush) { 164 | DataObject::reset(); 165 | } 166 | } 167 | 168 | /** 169 | * @param bool $flush 170 | * @return void 171 | */ 172 | public static function restoreSubsite($flush = null) 173 | { 174 | if (!self::usesSubsite()) { 175 | return; 176 | } 177 | Subsite::changeSubsite(self::$previousSubsite, $flush); 178 | } 179 | 180 | /** 181 | * @return array 182 | */ 183 | public static function listSubsites() 184 | { 185 | if (!self::usesSubsite()) { 186 | return []; 187 | } 188 | return Subsite::get()->map(); 189 | } 190 | 191 | /** 192 | * Execute the callback in given subsite 193 | * 194 | * @param int $ID Subsite ID or 0 for main site 195 | * @param callable $cb 196 | * @return void 197 | */ 198 | public static function withSubsite($ID, $cb) 199 | { 200 | $currentID = self::currentSubsiteID(); 201 | SubsiteState::singleton()->setSubsiteId($ID); 202 | $cb(); 203 | SubsiteState::singleton()->setSubsiteId($currentID); 204 | } 205 | 206 | /** 207 | * Execute the callback in all subsites 208 | * 209 | * @param callable $cb 210 | * @param bool $încludeMainSite 211 | * @return void 212 | */ 213 | public static function withSubsites($cb, $includeMainSite = true) 214 | { 215 | if (!self::usesSubsite()) { 216 | $cb(); 217 | return; 218 | } 219 | 220 | if ($includeMainSite) { 221 | SubsiteState::singleton()->setSubsiteId(0); 222 | $cb(0); 223 | } 224 | 225 | $currentID = self::currentSubsiteID(); 226 | $subsites = Subsite::get(); 227 | foreach ($subsites as $subsite) { 228 | // TODO: maybe use changeSubsite instead? 229 | SubsiteState::singleton()->setSubsiteId($subsite->ID); 230 | $cb($subsite->ID); 231 | } 232 | SubsiteState::singleton()->setSubsiteId($currentID); 233 | } 234 | 235 | public static function SiteConfig($SubsiteID = 0) 236 | { 237 | if (!$SubsiteID) { 238 | $SubsiteID = self::currentSubsiteID(); 239 | } 240 | $SiteConfig = SiteConfig::get()->setDataQueryParam('Subsite.Filter', false)->filter( 241 | [ 242 | 'SubsiteID' => $SubsiteID, 243 | ] 244 | )->first(); 245 | if (!$SiteConfig) { 246 | $SiteConfig = SiteConfig::current_site_config(); 247 | } 248 | if (!$SiteConfig) { 249 | $SiteConfig = new SiteConfig(); 250 | } 251 | return $SiteConfig; 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/Tasks/ClearCacheFolderTask.php: -------------------------------------------------------------------------------- 1 | request = $request; 21 | 22 | $folder = Director::baseFolder() . '/silverstripe-cache'; 23 | $create = $_GET['create'] ?? false; 24 | if (!is_dir($folder)) { 25 | if ($create) { 26 | mkdir($folder, 0755); 27 | } else { 28 | throw new Exception("silverstripe-cache folder does not exist in root"); 29 | } 30 | } 31 | 32 | $result = FileHelper::rmDir($folder); 33 | if ($result) { 34 | $this->message("Removed $folder"); 35 | } else { 36 | $this->message("Failed to remove $folder", "error"); 37 | } 38 | $result = mkdir($folder, 0755); 39 | if ($result) { 40 | $this->message("A new folder has been created at $folder"); 41 | } else { 42 | $this->message("Failed to create a new folder at $folder", "error"); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Tasks/DisabledMigrationTask.php: -------------------------------------------------------------------------------- 1 | request = $request; 33 | 34 | $this->addOption("go", "Tick this to proceed", false); 35 | $this->addOption("remove_files", "Remove db files", false); 36 | $this->addOption("remove_local", "Remove local files", false); 37 | 38 | $options = $this->askOptions(); 39 | 40 | $go = $options['go']; 41 | $remove_files = $options['remove_files']; 42 | $remove_local = $options['remove_local']; 43 | 44 | if (!$go) { 45 | echo ('Previewing what this task is about to do.'); 46 | } else { 47 | echo ("Let's clean this up!"); 48 | } 49 | echo ('
'; 53 | print_r($result); 54 | echo ''; 55 | } else { 56 | $this->msg("Opcache is disabled. It should be enabled to ensure optimal performances", "error"); 57 | } 58 | } 59 | protected function testMemcache() 60 | { 61 | if (!class_exists('Memcache')) { 62 | $this->msg("Memcache class does not exist. Make sure that the Memcache extension is installed"); 63 | } 64 | 65 | $host = defined('MEMCACHE_HOST') ? MEMCACHE_HOST : 'localhost'; 66 | $port = defined('MEMCACHE_PORT') ? MEMCACHE_PORT : 11211; 67 | 68 | $memcache = new \Memcache; 69 | $connected = $memcache->connect($host, $port); 70 | 71 | if ($connected) { 72 | $this->msg("Server's version: " . $memcache->getVersion()); 73 | 74 | $result = $memcache->get("key"); 75 | 76 | if ($result) { 77 | $this->msg("Data found in cache"); 78 | } else { 79 | $this->msg("Data not found in cache"); 80 | $tmp_object = new stdClass; 81 | $tmp_object->str_attr = "test"; 82 | $tmp_object->int_attr = 123; 83 | $tmp_object->time = time(); 84 | $tmp_object->date = date('Y-m-d H:i:s'); 85 | $tmp_object->arr = array(1, 2, 3); 86 | $memcache->set("key", $tmp_object, false, 10); 87 | } 88 | 89 | $this->msg("Store data in the cache (data will expire in 10 seconds)"); 90 | $this->msg("Data from the cache:"); 91 | echo '
'; 92 | var_dump($memcache->get("key")); 93 | echo ''; 94 | } else { 95 | $this->msg("Failed to connect"); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/TypographyController.php: -------------------------------------------------------------------------------- 1 | Title = 'Typography test page'; 38 | $this->ExtraMeta .= ''; 39 | 40 | return $this->renderWith(array('Typography', 'Page')); 41 | } 42 | public function RandomImage() 43 | { 44 | return Image::get()->sort('RAND()')->first(); 45 | } 46 | public function TypoForm() 47 | { 48 | $array = array('green', 'yellow', 'blue', 'pink', 'orange'); 49 | $form = new Form( 50 | $this, 51 | 'TestForm', 52 | $fields = FieldList::create( 53 | HeaderField::create('HeaderField1', 'HeaderField Level 1', 1), 54 | LiteralField::create('LiteralField', '
All fields up to EmailField are required and should be marked as such
'), 55 | TextField::create('TextField1', 'Text Field Example 1'), 56 | TextField::create('TextField2', 'Text Field Example 2'), 57 | TextField::create('TextField3', 'Text Field Example 3'), 58 | TextField::create('TextField4', ''), 59 | HeaderField::create('FieldGroupHdr', 'First/last name FieldGroup'), 60 | FieldGroup::create( 61 | TextField::create('FirstName1', 'First Name'), 62 | TextField::create('LastName1', 'Last Name') 63 | ), 64 | HeaderField::create('HeaderField2b', 'Field with right title', 2), 65 | TextareaField::create('TextareaField', 'Textarea Field') 66 | ->setColumns(45) 67 | ->setRightTitle('This is the right title'), 68 | EmailField::create('EmailField', 'Email address'), 69 | HeaderField::create('HeaderField2c', 'HeaderField Level 2', 2), 70 | DropdownField::create('DropdownField', 'Dropdown Field', array(0 => '-- please select --', 1 => 'test AAAA', 2 => 'test BBBB')), 71 | OptionsetField::create('OptionSF', 'Optionset Field', $array), 72 | CheckboxSetField::create('CheckboxSF', 'Checkbox Set Field', $array), 73 | CurrencyField::create('CurrencyField', 'Bling bling', '$123.45'), 74 | HeaderField::create('HeaderField3', 'Other Fields', 3), 75 | NumericField::create('NumericField', 'Numeric Field '), 76 | DateField::create('DateField', 'Date Field'), 77 | DateTimeField::create('DateTimeField', 'Date and Time Field'), 78 | CheckboxField::create('CheckboxField', 'Checkbox Field') 79 | ), 80 | $actions = FieldList::create( 81 | FormAction::create('submit', 'Submit Button') 82 | ), 83 | $requiredFields = RequiredFields::create( 84 | 'TextField1', 85 | 'TextField2', 86 | 'TextField3', 87 | 'ErrorField1', 88 | 'ErrorField2', 89 | 'EmailField', 90 | 'TextField3', 91 | 'RightTitleField', 92 | 'CheckboxField', 93 | 'CheckboxSetField' 94 | ) 95 | ); 96 | $form->setMessage('warning message', 'warning'); 97 | return $form; 98 | } 99 | public function TestForm($data) 100 | { 101 | $this->redirectBack(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /templates/Layout/Typography.ss: -------------------------------------------------------------------------------- 1 |