| Total Complexity | 51 |
| Total Lines | 359 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like AppPage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use AppPage, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 19 | class AppPage |
||
| 20 | { |
||
| 21 | /** |
||
| 22 | * The constructor |
||
| 23 | * |
||
| 24 | * @param DriverInterface $driver |
||
| 25 | * @param Utils $utils |
||
| 26 | */ |
||
| 27 | public function __construct(public DriverInterface $driver, protected Utils $utils) |
||
| 28 | {} |
||
| 29 | |||
| 30 | /** |
||
| 31 | * Name in title and navigation |
||
| 32 | * |
||
| 33 | * @return string |
||
| 34 | */ |
||
| 35 | public function name(): string |
||
| 36 | { |
||
| 37 | return '<span class="jaxon_dbadmin_name">Jaxon DbAdmin</span>'; |
||
| 38 | } |
||
| 39 | |||
| 40 | /** |
||
| 41 | * Get a target="_blank" attribute |
||
| 42 | * |
||
| 43 | * @return string |
||
| 44 | */ |
||
| 45 | public function blankTarget(): string |
||
| 46 | { |
||
| 47 | return ' target="_blank" rel="noreferrer noopener"'; |
||
| 48 | } |
||
| 49 | |||
| 50 | /** |
||
| 51 | * Get escaped error message |
||
| 52 | * |
||
| 53 | * @return string |
||
| 54 | */ |
||
| 55 | public function error(): string |
||
| 56 | { |
||
| 57 | return $this->utils->html($this->driver->error()); |
||
| 58 | } |
||
| 59 | |||
| 60 | /** |
||
| 61 | * Table caption used in navigation and headings |
||
| 62 | * |
||
| 63 | * @param TableEntity $table |
||
| 64 | * |
||
| 65 | * @return string |
||
| 66 | */ |
||
| 67 | public function tableName(TableEntity $table): string |
||
| 68 | { |
||
| 69 | return $this->utils->html($table->name); |
||
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * Field caption used in select and edit |
||
| 74 | * |
||
| 75 | * @param TableFieldEntity $field Single field returned from fields() |
||
| 76 | * @param int $order Order of column in select |
||
| 77 | * |
||
| 78 | * @return string |
||
| 79 | */ |
||
| 80 | public function fieldName(TableFieldEntity $field, /** @scrutinizer ignore-unused */ int $order = 0): string |
||
| 81 | { |
||
| 82 | return '<span title="' . $this->utils->html($field->fullType) . '">' . |
||
| 83 | $this->utils->html($field->name) . '</span>'; |
||
| 84 | } |
||
| 85 | |||
| 86 | /** |
||
| 87 | * Check if field should be shortened |
||
| 88 | * |
||
| 89 | * @param TableFieldEntity $field |
||
| 90 | * |
||
| 91 | * @return bool |
||
| 92 | */ |
||
| 93 | public function isShortable(TableFieldEntity $field): bool |
||
| 94 | { |
||
| 95 | $pattern = '~char|text|json|lob|geometry|point|linestring|polygon|string|bytea~'; |
||
| 96 | return preg_match($pattern, $field->type) > 0; |
||
| 97 | } |
||
| 98 | |||
| 99 | /** |
||
| 100 | * Apply SQL function |
||
| 101 | * |
||
| 102 | * @param string $function |
||
| 103 | * @param string $column escaped column identifier |
||
| 104 | * |
||
| 105 | * @return string |
||
| 106 | */ |
||
| 107 | public function applySqlFunction(string $function, string $column): string |
||
| 108 | { |
||
| 109 | if (!$function) { |
||
| 110 | return $column; |
||
| 111 | } |
||
| 112 | if ($function === 'unixepoch') { |
||
| 113 | return "DATETIME($column, '$function')"; |
||
| 114 | } |
||
| 115 | if ($function === 'count distinct') { |
||
| 116 | return "COUNT(DISTINCT $column)"; |
||
| 117 | } |
||
| 118 | return strtoupper($function) . "($column)"; |
||
| 119 | } |
||
| 120 | |||
| 121 | /** |
||
| 122 | * Value printed in select table |
||
| 123 | * |
||
| 124 | * @param mixed $value HTML-escaped value to print |
||
| 125 | * @param string $type Field type |
||
| 126 | * @param mixed $original Original value before escaping |
||
| 127 | * |
||
| 128 | * @return string |
||
| 129 | */ |
||
| 130 | private function getSelectFieldValue($value, string $type, $original): string |
||
| 131 | { |
||
| 132 | return match(true) { |
||
| 133 | $value === null => '<i>NULL</i>', |
||
| 134 | preg_match('~char|binary|boolean~', $type) && |
||
| 135 | !preg_match('~var~', $type) => "<code>$value</code>", |
||
| 136 | preg_match('~blob|bytea|raw|file~', $type) && |
||
| 137 | !$this->utils->str->isUtf8($value) => '<i>' . |
||
| 138 | $this->utils->trans->lang('%d byte(s)', strlen($original)) . '</i>', |
||
| 139 | preg_match('~json~', $type) => "<code>$value</code>", |
||
| 140 | $this->utils->isMail($value) => '<a href="' . |
||
| 141 | $this->utils->html("mailto:$value") . '">' . $value . '</a>', |
||
| 142 | // IE 11 and all modern browsers hide referrer |
||
| 143 | $this->utils->isUrl($value) => '<a href="' . $this->utils->html($value) . |
||
| 144 | '"' . $this->blankTarget() . '>' . $value . '</a>', |
||
| 145 | default => $value, |
||
| 146 | }; |
||
| 147 | } |
||
| 148 | |||
| 149 | /** |
||
| 150 | * Format value to use in select |
||
| 151 | * |
||
| 152 | * @param TableFieldEntity $field |
||
| 153 | * @param int|string|null $textLength |
||
| 154 | * @param mixed $value |
||
| 155 | * |
||
| 156 | * @return string |
||
| 157 | */ |
||
| 158 | public function selectValue(TableFieldEntity $field, $textLength, $value): string |
||
| 159 | { |
||
| 160 | // if (\is_array($value)) { |
||
| 161 | // $expression = ''; |
||
| 162 | // foreach ($value as $k => $v) { |
||
| 163 | // $expression .= '<tr>' . ($value != \array_values($value) ? |
||
| 164 | // '<th>' . $this->utils->html($k) : |
||
| 165 | // '') . '<td>' . $this->selectValue($field, $v, $textLength); |
||
| 166 | // } |
||
| 167 | // return "<table cellspacing='0'>$expression</table>"; |
||
| 168 | // } |
||
| 169 | // if (!$link) { |
||
| 170 | // $link = $this->selectLink($value, $field); |
||
| 171 | // } |
||
| 172 | $expression = $value; |
||
| 173 | if (!empty($expression)) { |
||
| 174 | if (!$this->utils->str->isUtf8($expression)) { |
||
| 175 | $expression = "\0"; // htmlspecialchars of binary data returns an empty string |
||
| 176 | } elseif ($textLength != '' && $this->isShortable($field)) { |
||
| 177 | // usage of LEFT() would reduce traffic but complicate query - |
||
| 178 | // expected average speedup: .001 s VS .01 s on local network |
||
| 179 | $expression = $this->utils->str->shortenUtf8($expression, max(0, +$textLength)); |
||
| 180 | } else { |
||
| 181 | $expression = $this->utils->html($expression); |
||
| 182 | } |
||
| 183 | } |
||
| 184 | return $this->getSelectFieldValue($expression, $field->type, $value); |
||
| 185 | } |
||
| 186 | |||
| 187 | /** |
||
| 188 | * @param TableFieldEntity $field |
||
| 189 | * @param int $textLength |
||
| 190 | * @param mixed $value |
||
| 191 | * |
||
| 192 | * @return array |
||
| 193 | */ |
||
| 194 | public function getFieldValue(TableFieldEntity $field, int $textLength, mixed $value): array |
||
| 195 | { |
||
| 196 | /*if ($value != "" && (!isset($email_fields[$key]) || $email_fields[$key] != "")) { |
||
| 197 | //! filled e-mails can be contained on other pages |
||
| 198 | $email_fields[$key] = ($this->page->isMail($value) ? $names[$key] : ""); |
||
| 199 | }*/ |
||
| 200 | return [ |
||
| 201 | // 'id', |
||
| 202 | 'text' => preg_match('~text|lob~', $field->type), |
||
| 203 | 'value' => $this->selectValue($field, $textLength, $value), |
||
| 204 | // 'editable' => false, |
||
| 205 | ]; |
||
| 206 | } |
||
| 207 | |||
| 208 | /** |
||
| 209 | * @param TableFieldEntity $field |
||
| 210 | * |
||
| 211 | * @return string |
||
| 212 | */ |
||
| 213 | public function getTableFieldType(TableFieldEntity $field): string |
||
| 214 | { |
||
| 215 | $type = $this->utils->str->html($field->fullType); |
||
| 216 | if ($field->null) { |
||
| 217 | $type .= ' <i>nullable</i>'; // ' <i>NULL</i>'; |
||
| 218 | } |
||
| 219 | if ($field->autoIncrement) { |
||
| 220 | $type .= ' <i>' . $this->utils->trans->lang('Auto Increment') . '</i>'; |
||
| 221 | } |
||
| 222 | if ($field->default !== '') { |
||
| 223 | $type .= /*' ' . $this->utils->trans->lang('Default value') .*/ ' [<b>' . |
||
| 224 | $this->utils->str->html($field->default) . '</b>]'; |
||
| 225 | } |
||
| 226 | return $type; |
||
| 227 | } |
||
| 228 | /** |
||
| 229 | * @param TableFieldEntity $field |
||
| 230 | * @param string $value |
||
| 231 | * @param string $function |
||
| 232 | * |
||
| 233 | * @return string |
||
| 234 | */ |
||
| 235 | private function getInputFieldExpression(TableFieldEntity $field, |
||
| 236 | string $value, string $function): string |
||
| 237 | { |
||
| 238 | $fieldName = $this->driver->escapeId($field->name); |
||
| 239 | $expression = $this->driver->quote($value); |
||
| 240 | |||
| 241 | if (preg_match('~^(now|getdate|uuid)$~', $function)) { |
||
| 242 | return "$function()"; |
||
| 243 | } |
||
| 244 | if (preg_match('~^current_(date|timestamp)$~', $function)) { |
||
| 245 | return $function; |
||
| 246 | } |
||
| 247 | if (preg_match('~^([+-]|\|\|)$~', $function)) { |
||
| 248 | return "$fieldName $function $expression"; |
||
| 249 | } |
||
| 250 | if (preg_match('~^[+-] interval$~', $function)) { |
||
| 251 | return "$fieldName $function " . |
||
| 252 | (preg_match("~^(\\d+|'[0-9.: -]') [A-Z_]+\$~i", $value) && |
||
| 253 | $this->driver->jush() !== "pgsql" ? $value : $expression); |
||
| 254 | } |
||
| 255 | if (preg_match('~^(addtime|subtime|concat)$~', $function)) { |
||
| 256 | return "$function($fieldName, $expression)"; |
||
| 257 | } |
||
| 258 | if (preg_match('~^(md5|sha1|password|encrypt)$~', $function)) { |
||
| 259 | return "$function($expression)"; |
||
| 260 | } |
||
| 261 | return $expression; |
||
| 262 | } |
||
| 263 | |||
| 264 | /** |
||
| 265 | * @param TableFieldEntity $field Single field from fields() |
||
| 266 | * @param string $value |
||
| 267 | * @param string $function |
||
| 268 | * |
||
| 269 | * @return string |
||
| 270 | */ |
||
| 271 | public function getUnconvertedFieldValue(TableFieldEntity $field, |
||
| 272 | string $value, string $function = ''): string |
||
| 273 | { |
||
| 274 | if ($function === 'SQL') { |
||
| 275 | return $value; // SQL injection |
||
| 276 | } |
||
| 277 | |||
| 278 | $expression = $this->getInputFieldExpression($field, $value, $function); |
||
| 279 | return $this->driver->unconvertField($field, $expression); |
||
| 280 | } |
||
| 281 | |||
| 282 | /** |
||
| 283 | * @param array $file |
||
| 284 | * @param string $key |
||
| 285 | * @param bool $decompress |
||
| 286 | * |
||
| 287 | * @return string |
||
| 288 | */ |
||
| 289 | public function readFileContent(array $file, string $key, bool $decompress): string |
||
| 290 | { |
||
| 291 | $name = $file['name'][$key]; |
||
| 292 | $tmpName = $file['tmp_name'][$key]; |
||
| 293 | $content = file_get_contents($decompress && preg_match('~\.gz$~', $name) ? |
||
| 294 | "compress.zlib://$tmpName" : $tmpName); //! may not be reachable because of open_basedir |
||
| 295 | if (!$decompress) { |
||
| 296 | return $content; |
||
| 297 | } |
||
| 298 | $start = substr($content, 0, 3); |
||
| 299 | if (function_exists('iconv') && preg_match("~^\xFE\xFF|^\xFF\xFE~", $start, $regs)) { |
||
| 300 | // not ternary operator to save memory |
||
| 301 | return iconv('utf-16', 'utf-8', $content) . "\n\n"; |
||
| 302 | } |
||
| 303 | if ($start == "\xEF\xBB\xBF") { // UTF-8 BOM |
||
| 304 | return substr($content, 3) . "\n\n"; |
||
| 305 | } |
||
| 306 | return $content; |
||
| 307 | } |
||
| 308 | |||
| 309 | /** |
||
| 310 | * Get file contents from $_FILES |
||
| 311 | * |
||
| 312 | * @param string $key |
||
| 313 | * @param bool $decompress |
||
| 314 | * |
||
| 315 | * @return string|null |
||
| 316 | */ |
||
| 317 | public function getFileContents(string $key, bool $decompress = false) |
||
| 336 | } |
||
| 337 | |||
| 338 | /** |
||
| 339 | * Returns export format options |
||
| 340 | * |
||
| 341 | * @return array |
||
| 342 | */ |
||
| 343 | public function dumpFormat(): array |
||
| 347 | // 'csv' => 'CSV,', |
||
| 348 | // 'csv;' => 'CSV;', |
||
| 349 | // 'tsv' => 'TSV', |
||
| 350 | ]; |
||
| 351 | } |
||
| 352 | |||
| 353 | /** |
||
| 354 | * Returns export output options |
||
| 355 | * |
||
| 356 | * @return array |
||
| 357 | */ |
||
| 358 | public function dumpOutput(): array |
||
| 368 | } |
||
| 369 | |||
| 370 | /** |
||
| 371 | * Set the path of the file for webserver load |
||
| 372 | * |
||
| 373 | * @return string |
||
| 374 | */ |
||
| 375 | public function importServerPath(): string |
||
| 378 | } |
||
| 379 | } |
||
| 380 |