Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 1 | <?php
|
| 2 | /*
|
| 3 | The match section performs AND operation on different matches: for example, if you have from and rcpt in the same rule,
|
| 4 | then the rule matches only when from AND rcpt match. For similar matches, the OR rule applies: if you have multiple rcpt matches,
|
| 5 | then any of these will trigger the rule. If a rule is triggered then no more rules are matched.
|
| 6 | */
|
| 7 | header('Content-Type: text/plain');
|
| 8 | require_once "vars.inc.php";
|
| 9 | // Getting headers sent by the client.
|
| 10 | ini_set('error_reporting', 0);
|
| 11 |
|
| 12 | //$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
| 13 | $dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
| 14 | $opt = [
|
| 15 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
| 16 | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
| 17 | PDO::ATTR_EMULATE_PREPARES => false,
|
| 18 | ];
|
| 19 | try {
|
| 20 | $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
| 21 | $stmt = $pdo->query("SELECT '1' FROM `filterconf`");
|
| 22 | }
|
| 23 | catch (PDOException $e) {
|
| 24 | echo 'settings { }';
|
| 25 | exit;
|
| 26 | }
|
| 27 |
|
| 28 | // Check if db changed and return header
|
Matthias Andreas Benkard | 12a5735 | 2021-12-28 18:02:04 +0100 | [diff] [blame^] | 29 | $stmt = $pdo->prepare("SELECT GREATEST(COALESCE(MAX(UNIX_TIMESTAMP(UPDATE_TIME)), 1), COALESCE(MAX(UNIX_TIMESTAMP(CREATE_TIME)), 1)) AS `db_update_time` FROM `information_schema`.`tables`
|
| 30 | WHERE (`TABLE_NAME` = 'filterconf' OR `TABLE_NAME` = 'settingsmap' OR `TABLE_NAME` = 'sogo_quick_contact' OR `TABLE_NAME` = 'alias')
|
Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 31 | AND TABLE_SCHEMA = :dbname;");
|
| 32 | $stmt->execute(array(
|
| 33 | ':dbname' => $database_name
|
| 34 | ));
|
| 35 | $db_update_time = $stmt->fetch(PDO::FETCH_ASSOC)['db_update_time'];
|
| 36 | if (empty($db_update_time)) {
|
| 37 | $db_update_time = 1572048000;
|
| 38 | }
|
| 39 | if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $db_update_time)) {
|
| 40 | header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 304);
|
| 41 | exit;
|
| 42 | } else {
|
| 43 | header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 200);
|
| 44 | }
|
Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 45 |
|
| 46 | function parse_email($email) {
|
| 47 | if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
| 48 | $a = strrpos($email, '@');
|
| 49 | return array('local' => substr($email, 0, $a), 'domain' => substr($email, $a));
|
| 50 | }
|
| 51 |
|
Matthias Andreas Benkard | 12a5735 | 2021-12-28 18:02:04 +0100 | [diff] [blame^] | 52 | function normalize_email($email) {
|
| 53 | $email = strtolower(str_replace('/', '\/', $email));
|
| 54 | $gm = "@gmail.com";
|
| 55 | if (substr_compare($email, $gm, -strlen($gm)) == 0) {
|
| 56 | $email = explode('@', $email);
|
| 57 | $email[0] = str_replace('.', '', $email[0]);
|
| 58 | $email = implode('@', $email);
|
| 59 | }
|
| 60 | $gm_alt = "@googlemail.com";
|
| 61 | if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) {
|
| 62 | $email = explode('@', $email);
|
| 63 | $email[0] = str_replace('.', '', $email[0]);
|
| 64 | $email[1] = str_replace('@', '', $gm);
|
| 65 | $email = implode('@', $email);
|
| 66 | }
|
| 67 | if (str_contains($email, "+")) {
|
| 68 | $email = explode('@', $email);
|
| 69 | $user = explode('+', $email[0]);
|
| 70 | $email[0] = $user[0];
|
| 71 | $email = implode('@', $email);
|
| 72 | }
|
| 73 | return $email;
|
| 74 | }
|
| 75 |
|
Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 76 | function wl_by_sogo() {
|
| 77 | global $pdo;
|
| 78 | $rcpt = array();
|
| 79 | $stmt = $pdo->query("SELECT DISTINCT(`sogo_folder_info`.`c_path2`) AS `user`, GROUP_CONCAT(`sogo_quick_contact`.`c_mail`) AS `contacts` FROM `sogo_folder_info`
|
| 80 | INNER JOIN `sogo_quick_contact` ON `sogo_quick_contact`.`c_folder_id` = `sogo_folder_info`.`c_folder_id`
|
| 81 | GROUP BY `c_path2`");
|
| 82 | $sogo_contacts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 83 | while ($row = array_shift($sogo_contacts)) {
|
| 84 | foreach (explode(',', $row['contacts']) as $contact) {
|
| 85 | if (!filter_var($contact, FILTER_VALIDATE_EMAIL)) {
|
| 86 | continue;
|
| 87 | }
|
| 88 | // Explicit from, no mime_from, no regex - envelope must match
|
| 89 | // mailcow white and blacklists also cover mime_from
|
Matthias Andreas Benkard | 12a5735 | 2021-12-28 18:02:04 +0100 | [diff] [blame^] | 90 | $rcpt[$row['user']][] = normalize_email($contact);
|
Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 91 | }
|
| 92 | }
|
| 93 | return $rcpt;
|
| 94 | }
|
| 95 |
|
| 96 | function ucl_rcpts($object, $type) {
|
| 97 | global $pdo;
|
| 98 | $rcpt = array();
|
| 99 | if ($type == 'mailbox') {
|
| 100 | // Standard aliases
|
| 101 | $stmt = $pdo->prepare("SELECT `address` FROM `alias`
|
| 102 | WHERE `goto` = :object_goto
|
| 103 | AND `address` NOT LIKE '@%'");
|
| 104 | $stmt->execute(array(
|
| 105 | ':object_goto' => $object
|
| 106 | ));
|
| 107 | $standard_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 108 | while ($row = array_shift($standard_aliases)) {
|
| 109 | $local = parse_email($row['address'])['local'];
|
| 110 | $domain = parse_email($row['address'])['domain'];
|
| 111 | if (!empty($local) && !empty($domain)) {
|
| 112 | $rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i';
|
| 113 | }
|
| 114 | $rcpt[] = str_replace('/', '\/', $row['address']);
|
| 115 | }
|
| 116 | // Aliases by alias domains
|
| 117 | $stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
|
| 118 | LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain`
|
| 119 | WHERE `mailbox`.`username` = :object");
|
| 120 | $stmt->execute(array(
|
| 121 | ':object' => $object
|
| 122 | ));
|
| 123 | $by_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 124 | array_filter($by_domain_aliases);
|
| 125 | while ($row = array_shift($by_domain_aliases)) {
|
| 126 | if (!empty($row['alias'])) {
|
| 127 | $local = parse_email($row['alias'])['local'];
|
| 128 | $domain = parse_email($row['alias'])['domain'];
|
| 129 | if (!empty($local) && !empty($domain)) {
|
| 130 | $rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i';
|
| 131 | }
|
| 132 | $rcpt[] = str_replace('/', '\/', $row['alias']);
|
| 133 | }
|
| 134 | }
|
| 135 | }
|
| 136 | elseif ($type == 'domain') {
|
| 137 | // Domain self
|
| 138 | $rcpt[] = '/.*@' . $object . '/i';
|
| 139 | $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
|
| 140 | WHERE `target_domain` = :object");
|
| 141 | $stmt->execute(array(':object' => $object));
|
| 142 | $alias_domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 143 | array_filter($alias_domains);
|
| 144 | while ($row = array_shift($alias_domains)) {
|
| 145 | $rcpt[] = '/.*@' . $row['alias_domain'] . '/i';
|
| 146 | }
|
| 147 | }
|
| 148 | return $rcpt;
|
| 149 | }
|
| 150 | ?>
|
| 151 | settings {
|
| 152 | watchdog {
|
| 153 | priority = 10;
|
| 154 | rcpt_mime = "/null@localhost/i";
|
| 155 | from_mime = "/watchdog@localhost/i";
|
| 156 | apply "default" {
|
| 157 | symbols_disabled = ["HISTORY_SAVE", "ARC", "ARC_SIGNED", "DKIM", "DKIM_SIGNED", "CLAM_VIRUS"];
|
| 158 | want_spam = yes;
|
| 159 | actions {
|
| 160 | reject = 9999.0;
|
| 161 | greylist = 9998.0;
|
| 162 | "add header" = 9997.0;
|
| 163 | }
|
| 164 |
|
| 165 | }
|
| 166 | }
|
| 167 | <?php
|
| 168 |
|
| 169 | /*
|
| 170 | // Start custom scores for users
|
| 171 | */
|
| 172 |
|
| 173 | $stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'highspamlevel' OR `option` = 'lowspamlevel'");
|
| 174 | $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 175 |
|
| 176 | while ($row = array_shift($rows)) {
|
| 177 | $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
|
| 178 | ?>
|
| 179 | score_<?=$username_sane;?> {
|
| 180 | priority = 4;
|
| 181 | <?php
|
| 182 | foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
| 183 | ?>
|
| 184 | rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
| 185 | <?php
|
| 186 | }
|
| 187 | $stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
|
| 188 | WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel')
|
| 189 | AND `object`= :object");
|
| 190 | $stmt->execute(array(':object' => $row['object']));
|
| 191 | $spamscore = $stmt->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP);
|
| 192 | ?>
|
| 193 | apply "default" {
|
| 194 | actions {
|
| 195 | reject = <?=$spamscore['highspamlevel'][0];?>;
|
| 196 | greylist = <?=$spamscore['lowspamlevel'][0] - 1;?>;
|
| 197 | "add header" = <?=$spamscore['lowspamlevel'][0];?>;
|
| 198 | }
|
| 199 | }
|
| 200 | }
|
| 201 | <?php
|
| 202 | }
|
| 203 |
|
| 204 | /*
|
| 205 | // Start SOGo contacts whitelist
|
| 206 | // Priority 4, lower than a domain whitelist (5) and lower than a mailbox whitelist (6)
|
| 207 | */
|
| 208 |
|
| 209 | foreach (wl_by_sogo() as $user => $contacts) {
|
| 210 | $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $user);
|
| 211 | ?>
|
| 212 | whitelist_sogo_<?=$username_sane;?> {
|
| 213 | <?php
|
| 214 | foreach ($contacts as $contact) {
|
| 215 | ?>
|
| 216 | from = <?=json_encode($contact, JSON_UNESCAPED_SLASHES);?>;
|
| 217 | <?php
|
| 218 | }
|
| 219 | ?>
|
| 220 | priority = 4;
|
| 221 | <?php
|
| 222 | foreach (ucl_rcpts($user, 'mailbox') as $rcpt) {
|
| 223 | ?>
|
| 224 | rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
| 225 | <?php
|
| 226 | }
|
| 227 | ?>
|
| 228 | apply "default" {
|
| 229 | SOGO_CONTACT = -99.0;
|
| 230 | }
|
| 231 | symbols [
|
| 232 | "SOGO_CONTACT"
|
| 233 | ]
|
| 234 | }
|
| 235 | <?php
|
| 236 | }
|
| 237 |
|
| 238 | /*
|
| 239 | // Start whitelist
|
| 240 | */
|
| 241 |
|
| 242 | $stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'whitelist_from'");
|
| 243 | $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 244 | while ($row = array_shift($rows)) {
|
| 245 | $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
|
| 246 | ?>
|
| 247 | whitelist_<?=$username_sane;?> {
|
| 248 | <?php
|
| 249 | $list_items = array();
|
| 250 | $stmt = $pdo->prepare("SELECT `value` FROM `filterconf`
|
| 251 | WHERE `object`= :object
|
| 252 | AND `option` = 'whitelist_from'");
|
| 253 | $stmt->execute(array(':object' => $row['object']));
|
| 254 | $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 255 | foreach ($list_items as $item) {
|
| 256 | ?>
|
| 257 | from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
|
| 258 | <?php
|
| 259 | }
|
| 260 | if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
| 261 | ?>
|
| 262 | priority = 5;
|
| 263 | <?php
|
| 264 | foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
| 265 | ?>
|
| 266 | rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
| 267 | <?php
|
| 268 | }
|
| 269 | }
|
| 270 | else {
|
| 271 | ?>
|
| 272 | priority = 6;
|
| 273 | <?php
|
| 274 | foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
| 275 | ?>
|
| 276 | rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
| 277 | <?php
|
| 278 | }
|
| 279 | }
|
| 280 | ?>
|
| 281 | apply "default" {
|
| 282 | MAILCOW_WHITE = -999.0;
|
| 283 | }
|
| 284 | symbols [
|
| 285 | "MAILCOW_WHITE"
|
| 286 | ]
|
| 287 | }
|
| 288 | whitelist_mime_<?=$username_sane;?> {
|
| 289 | <?php
|
| 290 | foreach ($list_items as $item) {
|
| 291 | ?>
|
| 292 | from_mime = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
|
| 293 | <?php
|
| 294 | }
|
| 295 | if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
| 296 | ?>
|
| 297 | priority = 5;
|
| 298 | <?php
|
| 299 | foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
| 300 | ?>
|
| 301 | rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
| 302 | <?php
|
| 303 | }
|
| 304 | }
|
| 305 | else {
|
| 306 | ?>
|
| 307 | priority = 6;
|
| 308 | <?php
|
| 309 | foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
| 310 | ?>
|
| 311 | rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
| 312 | <?php
|
| 313 | }
|
| 314 | }
|
| 315 | ?>
|
| 316 | apply "default" {
|
| 317 | MAILCOW_WHITE = -999.0;
|
| 318 | }
|
| 319 | symbols [
|
| 320 | "MAILCOW_WHITE"
|
| 321 | ]
|
| 322 | }
|
| 323 | <?php
|
| 324 | }
|
| 325 |
|
| 326 | /*
|
| 327 | // Start blacklist
|
| 328 | */
|
| 329 |
|
| 330 | $stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'blacklist_from'");
|
| 331 | $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 332 | while ($row = array_shift($rows)) {
|
| 333 | $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
|
| 334 | ?>
|
| 335 | blacklist_<?=$username_sane;?> {
|
| 336 | <?php
|
| 337 | $list_items = array();
|
| 338 | $stmt = $pdo->prepare("SELECT `value` FROM `filterconf`
|
| 339 | WHERE `object`= :object
|
| 340 | AND `option` = 'blacklist_from'");
|
| 341 | $stmt->execute(array(':object' => $row['object']));
|
| 342 | $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 343 | foreach ($list_items as $item) {
|
| 344 | ?>
|
| 345 | from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
|
| 346 | <?php
|
| 347 | }
|
| 348 | if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
| 349 | ?>
|
| 350 | priority = 5;
|
| 351 | <?php
|
| 352 | foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
| 353 | ?>
|
| 354 | rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
| 355 | <?php
|
| 356 | }
|
| 357 | }
|
| 358 | else {
|
| 359 | ?>
|
| 360 | priority = 6;
|
| 361 | <?php
|
| 362 | foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
| 363 | ?>
|
| 364 | rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
| 365 | <?php
|
| 366 | }
|
| 367 | }
|
| 368 | ?>
|
| 369 | apply "default" {
|
| 370 | MAILCOW_BLACK = 999.0;
|
| 371 | }
|
| 372 | symbols [
|
| 373 | "MAILCOW_BLACK"
|
| 374 | ]
|
| 375 | }
|
| 376 | blacklist_header_<?=$username_sane;?> {
|
| 377 | <?php
|
| 378 | foreach ($list_items as $item) {
|
| 379 | ?>
|
| 380 | from_mime = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
|
| 381 | <?php
|
| 382 | }
|
| 383 | if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
| 384 | ?>
|
| 385 | priority = 5;
|
| 386 | <?php
|
| 387 | foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
| 388 | ?>
|
| 389 | rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
| 390 | <?php
|
| 391 | }
|
| 392 | }
|
| 393 | else {
|
| 394 | ?>
|
| 395 | priority = 6;
|
| 396 | <?php
|
| 397 | foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
| 398 | ?>
|
| 399 | rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
| 400 | <?php
|
| 401 | }
|
| 402 | }
|
| 403 | ?>
|
| 404 | apply "default" {
|
| 405 | MAILCOW_BLACK = 999.0;
|
| 406 | }
|
| 407 | symbols [
|
| 408 | "MAILCOW_BLACK"
|
| 409 | ]
|
| 410 | }
|
| 411 | <?php
|
| 412 | }
|
| 413 |
|
| 414 | /*
|
| 415 | // Start traps
|
| 416 | */
|
| 417 |
|
| 418 | ?>
|
| 419 | ham_trap {
|
| 420 | <?php
|
| 421 | foreach (ucl_rcpts('ham@localhost', 'mailbox') as $rcpt) {
|
| 422 | ?>
|
| 423 | rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
| 424 | <?php
|
| 425 | }
|
| 426 | ?>
|
| 427 | priority = 9;
|
| 428 | apply "default" {
|
| 429 | symbols_enabled = ["HISTORY_SAVE"];
|
| 430 | }
|
| 431 | symbols [
|
| 432 | "HAM_TRAP"
|
| 433 | ]
|
| 434 | }
|
| 435 |
|
| 436 | spam_trap {
|
| 437 | <?php
|
| 438 | foreach (ucl_rcpts('spam@localhost', 'mailbox') as $rcpt) {
|
| 439 | ?>
|
| 440 | rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
| 441 | <?php
|
| 442 | }
|
| 443 | ?>
|
| 444 | priority = 9;
|
| 445 | apply "default" {
|
| 446 | symbols_enabled = ["HISTORY_SAVE"];
|
| 447 | }
|
| 448 | symbols [
|
| 449 | "SPAM_TRAP"
|
| 450 | ]
|
| 451 | }
|
| 452 | <?php
|
| 453 | // Start additional content
|
| 454 |
|
| 455 | $stmt = $pdo->query("SELECT `id`, `content` FROM `settingsmap` WHERE `active` = '1'");
|
| 456 | $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
| 457 | while ($row = array_shift($rows)) {
|
| 458 | $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['id']);
|
| 459 | ?>
|
| 460 | additional_settings_<?=intval($row['id']);?> {
|
| 461 | <?php
|
| 462 | $content = preg_split('/\r\n|\r|\n/', $row['content']);
|
| 463 | foreach ($content as $line) {
|
| 464 | echo ' ' . $line . PHP_EOL;
|
| 465 | }
|
| 466 | ?>
|
| 467 | }
|
| 468 | <?php
|
| 469 | }
|
| 470 | ?>
|
| 471 | }
|