File modules/cms/model/Element.class.php

Last commit: Sat Oct 17 01:14:30 2026 +0200	Jan Dankert	New: Using a structured class for the API-Response. The output for GET-Requests is now without the "output" layer.
1 <?php 2 namespace cms\model; 3 4 5 use cms\base\DB as Db; 6 use cms\generator\FileContext; 7 use cms\generator\link\PreviewLink; 8 use cms\generator\Producer; 9 use util\ArrayUtils; 10 use logger\Logger; 11 use util\YAML; 12 13 /** 14 * Element. 15 * 16 * An element is a placeholder in a template. 17 * It is of a specific type, e.g. text, image, ... 18 * 19 * @author Jan Dankert 20 */ 21 class Element extends ModelBase 22 { 23 // the list of all available element types: 24 const ELEMENT_TYPE_DATETIME = 1; 25 const ELEMENT_TYPE_NUMBER = 2; 26 const ELEMENT_TYPE_TEXT = 3; 27 const ELEMENT_TYPE_INFO = 4; 28 const ELEMENT_TYPE_INFODATE = 5; 29 const ELEMENT_TYPE_LINK = 6; 30 const ELEMENT_TYPE_LONGTEXT = 7; 31 const ELEMENT_TYPE_CODE = 8; 32 const ELEMENT_TYPE_DYNAMIC = 9; 33 const ELEMENT_TYPE_SELECT = 10; 34 const ELEMENT_TYPE_COPY = 11; 35 const ELEMENT_TYPE_LINKINFO = 12; 36 const ELEMENT_TYPE_LINKDATE = 13; 37 const ELEMENT_TYPE_INSERT = 14; 38 39 /** 40 * Data. 41 */ 42 const ELEMENT_TYPE_DATA = 15; 43 44 /** 45 * Coordinates. 46 */ 47 const ELEMENT_TYPE_COORD = 16; 48 49 const ELEMENT_TYPE_CHECKBOX = 17; 50 const ELEMENT_TYPE_URL = 18; 51 const ELEMENT_TYPE_DATE = 19; 52 const ELEMENT_TYPE_TIME = 20; 53 const ELEMENT_TYPE_COLOR = 21; 54 const ELEMENT_TYPE_EMAIL = 22; 55 const ELEMENT_TYPE_WEEK = 23; 56 const ELEMENT_TYPE_MONTH = 24; 57 const ELEMENT_TYPE_TEL = 25; 58 const ELEMENT_TYPE_RANGE = 26; 59 const ELEMENT_TYPE_PASSWORD = 27; 60 const ELEMENT_TYPE_SELECTS = 28; 61 62 const ELEMENT_FORMAT_TEXT = 0; 63 const ELEMENT_FORMAT_HTML = 1; 64 const ELEMENT_FORMAT_WIKI = 2; 65 const ELEMENT_FORMAT_MARKDOWN = 3; 66 const ELEMENT_FORMAT_HTML_SOURCE = 4; 67 68 const ELEMENT_FLAG_HTML_ALLOWED = 1; 69 const ELEMENT_FLAG_ALL_LANGUAGES = 2; 70 const ELEMENT_FLAG_WRITABLE = 4; 71 const ELEMENT_FLAG_WITH_ICON = 8; 72 const ELEMENT_FLAG_INHERIT = 16; 73 74 /** 75 * Eindeutige ID dieses Elementes 76 * @type Integer 77 */ 78 var $elementid; 79 80 /** 81 * Template-ID zu der dieses Elementes geh?rt 82 * @type Integer 83 */ 84 var $templateid; 85 86 /** 87 * Typ des Elementes 88 * Folgende Typen sind moeglich: 89 * <ul> 90 * <li>text</li> 91 * <li>longtext</li> 92 * <li>select</li> 93 * <li>number</li> 94 * <li>link</li> 95 * <li>date</li> 96 * <li>insert</li> 97 * <li>linkinfo</li> 98 * <li>linkdate</li> 99 * <li>code</li> 100 * <li>info</li> 101 * <li>infodate</li> 102 * </ul> 103 * 104 * @type String 105 * @deprecated use #typeid 106 */ 107 var $type; 108 109 /** 110 * 111 * Type of the element. Must be a constant value of ELEMENT_TYPE_*. 112 * @var integer Type of element 113 */ 114 public $typeid; 115 116 /** 117 * Logischer Name dieses Elementes 118 * @type String 119 */ 120 var $name; 121 122 /** 123 * Eingabefeld-Bezeichnung für dieses Element. 124 * @type String 125 */ 126 var $label; 127 128 /** 129 * Beschreibung zu diesem Element 130 * Zu jedem Element kann eine Beschreibung hinterlegt werden, die dem Redakteur bei der Bearbeitung 131 * der Inhalte als Bearbeitungshilfe dienen kann. 132 * @type String 133 */ 134 var $desc; 135 136 /** 137 * Objekt-ID eines Ordners, aus diesem Ordner (samt Unterordner) 138 * k?nnen zu verlinkende Objekte ausgew?hlt werden 139 * @type Integer 140 */ 141 var $folderObjectId = 0; 142 143 /** 144 * Vorausgew�hltes Objekt. 145 * @type Integer 146 */ 147 var $defaultObjectId = 0; 148 149 /** 150 * Schalter ob dieses Element von Redakteuren bearbeiten werden kann 151 * @type bool 152 */ 153 public bool $writable; 154 155 /** 156 * values are inherited from parent nodes. 157 * @var bool 158 */ 159 public bool $inherit; 160 161 /** 162 * Schalter, ob dieses Element in allen Sprachen den gleichen Inhalt haben soll 163 * @type bool 164 */ 165 public bool $allLanguages; 166 167 public static $readonlyElementTypeIds = array( 168 self::ELEMENT_TYPE_COPY,self::ELEMENT_TYPE_LINKINFO,self::ELEMENT_TYPE_LINKDATE,self::ELEMENT_TYPE_INFO,self::ELEMENT_TYPE_INFODATE,self::ELEMENT_TYPE_CODE,self::ELEMENT_TYPE_DYNAMIC 169 ); 170 171 172 /** 173 * Untertyp. 174 * 175 * @var String 176 */ 177 var $subtype = ''; 178 179 /** 180 * @var bool 181 */ 182 var bool $withIcon = false; 183 var $dateformat = 'r'; 184 185 /* 186 * @deprecated use format. 187 */ 188 public $wiki = false; 189 190 public $format = self::ELEMENT_FORMAT_TEXT; 191 192 /** 193 * @var bool 194 */ 195 public bool $html = false; 196 197 var $decimals = 0; 198 var $decPoint = '.'; 199 var $thousandSep = ''; 200 var $code = ''; 201 var $defaultText = ''; 202 203 204 /** 205 * Im Konstruktor wird die Element-Id gesetzt 206 * @param Integer Element-Id 207 */ 208 function __construct( $elementid=0 ) 209 { 210 if ( intval($elementid)!=0 ) 211 $this->elementid = $elementid; 212 } 213 214 215 /** 216 * Hinzuf?gen eines Elementes 217 * Das aktuelle Element wird in die Datenbank geschrieben. 218 */ 219 function add() 220 { 221 $sql = Db::sql('SELECT MAX(id) FROM {{element}}'); 222 $this->elementid = intval($sql->getOne())+1; 223 224 $sql = Db::sql( 'INSERT INTO {{element}}'. 225 ' (id,templateid,name,label,descr,typeid,flags) '. 226 " VALUES ( {elementid},{templateid},{name},{label},{description},{typeid},{flags} ) " ); 227 228 $flags = 0; 229 $flags += self::ELEMENT_FLAG_WRITABLE * intval($this->writable); 230 231 $sql->setInt ( 'elementid' ,$this->elementid ); 232 $sql->setString ( 'name' ,$this->name ); 233 $sql->setString ( 'label' ,$this->label ); 234 $sql->setInt ( 'typeid' ,$this->typeid ); 235 $sql->setInt ( 'templateid' ,$this->templateid ); 236 $sql->setInt ( 'flags' ,$flags ); 237 $sql->setString ( 'description',$this->desc ); 238 239 $sql->execute(); 240 } 241 242 243 /** 244 * Lesen des Elementes aus der Datenbank 245 * Alle Eigenschaften des Elementes werden aus der Datenbank gelesen 246 * @throws \util\exception\ObjectNotFoundException 247 */ 248 function load() 249 { 250 if ( intval($this->elementid) != 0 ) 251 { 252 $db = Db::get(); 253 $sql = $db->sql( <<<SQL 254 SELECT * FROM {{element}} 255 WHERE id={elementid} 256 SQL 257 ); 258 $sql->setInt( 'elementid',$this->elementid ); 259 $this->setDatabaseRow( $sql->getRow() ); 260 } 261 } 262 263 264 /** 265 * @param $prop 266 * @throws \util\exception\ObjectNotFoundException 267 */ 268 function setDatabaseRow($prop ) 269 { 270 if ( count($prop) <= 0 ) 271 throw new \util\exception\ObjectNotFoundException("Element not found"); 272 273 $this->elementid = $prop['id' ]; 274 $this->templateid = $prop['templateid']; 275 $this->name = $prop['name' ]; 276 $this->label = $prop['label' ]; 277 $this->desc = $prop['descr' ]; 278 $this->typeid = $prop['typeid' ]; 279 $this->type = Element::getAvailableTypes()[ $this->typeid ]; // name of type 280 $this->subtype = $prop['subtype' ]; 281 282 $this->dateformat = $prop['dateformat']; 283 $this->wiki = $prop['format'] == self::ELEMENT_FORMAT_WIKI; 284 $this->format = $prop['format']; 285 $this->withIcon = (bool) ($prop['flags'] & self::ELEMENT_FLAG_WITH_ICON ); 286 $this->html = (bool) ($prop['flags'] & self::ELEMENT_FLAG_HTML_ALLOWED ); 287 $this->allLanguages = (bool) ($prop['flags'] & self::ELEMENT_FLAG_ALL_LANGUAGES); 288 $this->writable = (bool) ($prop['flags'] & self::ELEMENT_FLAG_WRITABLE ); 289 $this->inherit = (bool) ($prop['flags'] & self::ELEMENT_FLAG_INHERIT ); 290 291 if ( !$this->writable) 292 $this->withIcon = false; 293 294 $this->decimals = intval( $prop['decimals' ] ); 295 $this->decPoint = strval( $prop['dec_point' ] ); 296 $this->thousandSep = strval( $prop['thousand_sep' ] ); 297 $this->code = strval( $prop['code' ] ); 298 $this->defaultText = strval( $prop['default_text' ] ); 299 $this->folderObjectId = intval( $prop['folderobjectid' ] ); 300 $this->defaultObjectId = intval( $prop['default_objectid'] ); 301 } 302 303 304 /** 305 * Abspeichern des Elementes 306 * Das aktuelle Element wird in der Datenbank gespeichert 307 */ 308 function save() 309 { 310 $sql = Db::sql( 'UPDATE {{element}}'. 311 ' SET templateid = {templateid},'. 312 ' name = {name},'. 313 ' label = {label},'. 314 ' descr = {desc},'. 315 ' typeid = {typeid},'. 316 ' subtype = {subtype},'. 317 ' dateformat = {dateformat},'. 318 ' flags = {flags},'. 319 ' format = {format},'. 320 ' decimals = {decimals},'. 321 ' dec_point = {decPoint},'. 322 ' thousand_sep = {thousandSep},'. 323 ' code = {code},'. 324 ' default_text = {defaultText},'. 325 ' folderobjectid = {folderObjectId},'. 326 ' default_objectid= {defaultObjectId}'. 327 ' WHERE id={elementid}' ); 328 329 $flags = 0; 330 $flags += self::ELEMENT_FLAG_WITH_ICON * intval($this->withIcon ); 331 $flags += self::ELEMENT_FLAG_HTML_ALLOWED * intval($this->html ); 332 $flags += self::ELEMENT_FLAG_ALL_LANGUAGES * intval($this->allLanguages); 333 $flags += self::ELEMENT_FLAG_WRITABLE * intval($this->writable ); 334 $flags += self::ELEMENT_FLAG_INHERIT * intval($this->inherit ); 335 336 $sql->setInt ( 'elementid' ,$this->elementid ); 337 $sql->setInt ( 'templateid' ,$this->templateid ); 338 $sql->setString ( 'name' ,$this->name ); 339 $sql->setString ( 'label' ,$this->label ); 340 $sql->setString ( 'desc' ,$this->desc ); 341 $sql->setInt ( 'typeid' ,$this->typeid ); 342 $sql->setString ( 'subtype' ,$this->subtype ); 343 $sql->setString ( 'dateformat' ,$this->dateformat ); 344 $sql->setInt ( 'flags' ,$flags ); 345 $sql->setInt ( 'format' ,$this->format ); 346 $sql->setInt ( 'decimals' ,$this->decimals ); 347 $sql->setString ( 'decPoint' ,$this->decPoint ); 348 $sql->setString ( 'thousandSep' ,$this->thousandSep ); 349 $sql->setString ( 'code' ,$this->code ); 350 $sql->setString ( 'defaultText' ,$this->defaultText ); 351 352 if ( intval($this->folderObjectId)==0 ) 353 $sql->setNull( 'folderObjectId' ); 354 else $sql->setInt ( 'folderObjectId' ,$this->folderObjectId ); 355 356 if ( intval($this->defaultObjectId)==0 ) 357 $sql->setNull( 'defaultObjectId' ); 358 else $sql->setInt ( 'defaultObjectId' ,$this->defaultObjectId ); 359 360 $sql->execute(); 361 } 362 363 364 365 /** 366 * Setzt ein Prefix vor den Elementnamen. 367 * @param String Prefix 368 */ 369 function setPrefix( $prefix ) 370 { 371 if ( strrpos($this->name,'%') === FALSE ) 372 $name = $this->name; 373 else 374 list( $oldprefix,$name ) = explode('%',$this->name.'%'); 375 376 $this->name = $prefix.'%'.$name; 377 } 378 379 380 /** 381 * Loeschen des Elementes und aller Inhalte 382 */ 383 public function delete() 384 { 385 $db = Db::get(); 386 387 // Inhalte loeschen. 388 // notwendig, damit die Fremdschlüsselbeziehungen auf diesen Element aufgehoben werden. 389 $this->deleteValues(); 390 391 // Element loeschen 392 $sql = $db->sql('DELETE FROM {{element}} '. 393 ' WHERE id={elementid}' ); 394 $sql->setInt( 'elementid',$this->elementid ); 395 396 $sql->execute(); 397 } 398 399 400 /** 401 * Deletes all values for this element. 402 */ 403 public function deleteValues() 404 { 405 $sql = DB::sql( <<<SQL 406 SELECT id as pagecontentid,contentid 407 FROM {{pagecontent}} 408 WHERE elementid = {elementid} 409 SQL 410 ); 411 $sql->setInt( 'elementid',$this->elementid ); 412 413 foreach( $sql->getAll() as $pagecontentRow ) { 414 $sql = DB::sql( <<<SQL 415 DELETE 416 FROM {{value}} 417 WHERE contentid = {contentid} 418 SQL 419 ); 420 $sql->setInt( 'contentid',$pagecontentRow['contentid'] ); 421 $sql->execute(); 422 423 424 $sql = DB::sql( <<<SQL 425 DELETE 426 FROM {{pagecontent}} 427 WHERE id = {pagecontentid} 428 SQL 429 ); 430 $sql->setInt( 'pagecontentid',$pagecontentRow['pagecontentid'] ); 431 $sql->execute(); 432 433 434 $sql = DB::sql( <<<SQL 435 DELETE 436 FROM {{content}} 437 WHERE id = {contentid} 438 SQL 439 ); 440 $sql->setInt( 'contentid',$pagecontentRow['contentid'] ); 441 $sql->execute(); 442 } 443 } 444 445 446 447 448 /** 449 * Default value 450 * @return string default value 451 */ 452 public function getDefaultValue() 453 { 454 switch( $this->typeid ) 455 { 456 case self::ELEMENT_TYPE_TEXT: 457 case self::ELEMENT_TYPE_LONGTEXT: 458 return $this->defaultText; 459 case self::ELEMENT_TYPE_INFO: 460 return '(i)'; 461 case self::ELEMENT_TYPE_LINKDATE: 462 case self::ELEMENT_TYPE_INFODATE: 463 case self::ELEMENT_TYPE_DATETIME: 464 case self::ELEMENT_TYPE_DATE: 465 case self::ELEMENT_TYPE_TIME: 466 case self::ELEMENT_TYPE_WEEK: 467 case self::ELEMENT_TYPE_MONTH: 468 return date($this->dateformat); 469 case self::ELEMENT_TYPE_LINK: 470 if ( $this->defaultObjectId ) { 471 $o = new BaseObject($this->defaultObjectId); 472 $o->load(); 473 switch( $o->typeid ) { 474 case BaseObject::TYPEID_FILE: 475 case BaseObject::TYPEID_IMAGE: 476 case BaseObject::TYPEID_TEXT: 477 $linkFormat = new PreviewLink( new FileContext($o->objectid,Producer::SCHEME_PREVIEW)); 478 return $linkFormat->linkToObject($o,$o); 479 case BaseObject::TYPEID_LINK: 480 return ""; 481 case BaseObject::TYPEID_URL: 482 $url = new Url( $o->objectid ); 483 $url->load(); 484 return $url->url; 485 } 486 } 487 return $this->getName(); 488 case 'number': 489 return '0'; 490 default: 491 return $this->getName(); 492 } 493 } 494 495 /** 496 * a textual representation for all element types. 497 * 498 * @return array id->name 499 */ 500 public static function getAvailableTypes() 501 { 502 return [ 503 self::ELEMENT_TYPE_TEXT => 'text', 504 self::ELEMENT_TYPE_URL => 'url', 505 self::ELEMENT_TYPE_TEL => 'tel', 506 self::ELEMENT_TYPE_EMAIL => 'mail', 507 self::ELEMENT_TYPE_LONGTEXT => 'longtext', 508 self::ELEMENT_TYPE_SELECT => 'select', 509 self::ELEMENT_TYPE_SELECTS => 'selects', 510 self::ELEMENT_TYPE_NUMBER => 'number', 511 self::ELEMENT_TYPE_RANGE => 'range', 512 self::ELEMENT_TYPE_CHECKBOX => 'checkbox', 513 self::ELEMENT_TYPE_LINK => 'link', 514 self::ELEMENT_TYPE_DATETIME => 'datetime', 515 self::ELEMENT_TYPE_DATE => 'date', 516 self::ELEMENT_TYPE_TIME => 'time', 517 self::ELEMENT_TYPE_WEEK => 'week', 518 self::ELEMENT_TYPE_MONTH => 'month', 519 self::ELEMENT_TYPE_INSERT => 'insert', 520 self::ELEMENT_TYPE_COPY => 'copy', 521 self::ELEMENT_TYPE_LINKINFO => 'linkinfo', 522 self::ELEMENT_TYPE_LINKDATE => 'linkdate', 523 self::ELEMENT_TYPE_CODE => 'code', 524 self::ELEMENT_TYPE_DYNAMIC => 'dynamic', 525 self::ELEMENT_TYPE_INFO => 'info', 526 self::ELEMENT_TYPE_INFODATE => 'infodate', 527 self::ELEMENT_TYPE_DATA => 'data', 528 self::ELEMENT_TYPE_COORD => 'coord', 529 self::ELEMENT_TYPE_COLOR => 'color', 530 self::ELEMENT_TYPE_PASSWORD => 'password' 531 ]; 532 } 533 534 535 /** 536 * all available formats for text elements. 537 * 538 * @return array id->name 539 */ 540 public static function getAvailableFormats() 541 { 542 return [ 543 self::ELEMENT_FORMAT_TEXT => 'text', 544 self::ELEMENT_FORMAT_WIKI => 'wiki', 545 self::ELEMENT_FORMAT_HTML => 'html', 546 self::ELEMENT_FORMAT_MARKDOWN => 'markdown', 547 self::ELEMENT_FORMAT_HTML_SOURCE => 'htmlsource', 548 ]; 549 } 550 551 552 /** 553 * Ermittelt die Klasse des Element-Typs.<br> 554 * Entweder "info", "text" oder "dynamic". 555 * 556 * @return String 557 */ 558 function getTypeClass() 559 { 560 switch( $this->type ) 561 { 562 case 'text': 563 case 'longtext': 564 case 'select': 565 case 'number': 566 case 'link': 567 case 'date': 568 case 'list': 569 case 'insert': 570 return 'text'; 571 572 case 'code': 573 case 'dynamic': 574 return 'dynamic'; 575 576 case 'copy': 577 case 'info': 578 case 'infodate': 579 case 'linkinfo': 580 case 'linkdate': 581 default: 582 return 'info'; 583 } 584 } 585 586 587 /** 588 * Gets all available items from the code. 589 * The Code must be valid YAML. 590 * @return string[] 591 */ 592 public function getSelectItems() 593 { 594 $items = $this->getDynamicParameters(); 595 596 if ( is_scalar( $items ) ) 597 return [ strval( $items ) ]; 598 599 if ( ! is_array( $items) ) 600 return []; 601 602 // Kick out unpleasant values like arrays or empty values. 603 $items = array_filter( $items,function($value) { 604 return $value!=null && $value !=='' && !is_array($value); 605 }); 606 607 // Make all values to string. 608 $items = array_map( function($value) { 609 return strval($value); 610 },$items); 611 612 return $items; 613 } 614 615 616 /** 617 * Get the data from the code. 618 * 619 * The code is parsed as YAML and returned as an array. 620 * 621 * @return array 622 */ 623 public function getDynamicParameters(): array 624 { 625 // Fixing old syntax ("key:value") to valid YAML syntax. 626 $this->code = preg_replace( '/^(\w+)\:(.+)$/m','${1}: ${2}', $this->code ); 627 628 return YAML::parse( $this->code ); 629 } 630 631 632 /** 633 * Ermittelt, ob das Element beschreibbar ist. 634 * Bestimmte Typen (z.B. Info-Felder) sind nie beschreibbar, dann wird immer false zur?ckgegeben. 635 * Ansonsten wird ermittelt, ob dieses Element als beschreibbar markiert ist. 636 */ 637 function isWritable(): bool 638 { 639 // Bei bestimmten Feldern immer false zurueckgeben 640 if ( in_array($this->typeid,Element::$readonlyElementTypeIds) ) 641 return false; 642 643 return $this->writable; 644 } 645 646 647 /** 648 * The technical name of this element type. 649 * 650 * @return String 651 */ 652 public function getTypeName(): string 653 { 654 return Element::getAvailableTypes()[ $this->typeid ]; // name of type 655 656 } 657 658 public function getName(): string 659 { 660 return $this->label; 661 } 662 663 664 public function getId(): int 665 { 666 return $this->elementid; 667 } 668 669 670 }
Download modules/cms/model/Element.class.php
History Sat, 17 Oct 2026 01:14:30 +0200 Jan Dankert New: Using a structured class for the API-Response. The output for GET-Requests is now without the "output" layer. Tue, 13 Oct 2026 18:49:59 +0200 Jan Dankert New: Another action which returns an object instead of single output values. This should be the future, but it's a long way to refactore all old action methods... Tue, 14 Jan 2025 01:57:22 +0100 Jan Dankert New: Some new template element types like 'url','email','range','password','date','time', ...; New: datalists for input fields; Refactoring: Cleaner templates and actions for editing values; Mon, 13 Jan 2025 21:08:50 +0100 Jan Dankert Fix: The old yaml-like syntax "key:value" is now supported for backward compatibility. Mon, 13 Jan 2025 21:01:10 +0100 Jan Dankert Refactoring: Parse the select items as YAML. This is more flexible and standardized. Wed, 17 Apr 2024 00:34:07 +0200 Jan Dankert New: Inputfields for coordinates; coordinates are stored in the now 64bit-number-field of the value. Thu, 2 Jun 2022 01:04:54 +0200 Jan Dankert New: Element type "checkbox" Wed, 1 Jun 2022 01:05:34 +0200 Jan Dankert New: Element types for "coordinates" and "data" Wed, 1 Jun 2022 00:11:40 +0200 Jan Dankert New: DSL as a filter for number values Wed, 25 May 2022 22:47:17 +0200 Jan Dankert New: DSL (domain specific language) for code elements. The old way with PHP code ist not sandboxed and unsecure. This approach is a minimalistic, javascript-like, scripting engine. For now only simple function calls are possible, for example: alert("example"); Fri, 18 Mar 2022 22:38:42 +0100 dankert Refactoring: Extracted the TemplateGenerator out of the PageGenerator. Mon, 6 Dec 2021 22:33:10 +0100 dankert Some fixes for deleting objects. Fri, 2 Apr 2021 00:12:48 +0200 Jan Dankert New: Valueformat "html source" Sun, 7 Mar 2021 00:10:20 +0100 Jan Dankert Refactoring: Hopefully more performance while accessing the database resultsets. Mon, 26 Oct 2020 23:09:24 +0100 Jan Dankert Cleanup UI for adding templates and adding projects. Fri, 2 Oct 2020 23:11:48 +0200 Jan Dankert Cleanup: No '.inputholder' any more, notices with links to objects. Tue, 29 Sep 2020 22:17:11 +0200 Jan Dankert Refactoring: Do not use global constants. Sat, 26 Sep 2020 04:03:53 +0200 Jan Dankert Refactoring: read language keys with a class. Sat, 26 Sep 2020 02:26:39 +0200 Jan Dankert Refactoring: No global functions any more, the database object is read from the Db class. Wed, 23 Sep 2020 01:04:05 +0200 Jan Dankert Cleanup of deprecated methods and deprecated class attributes. Sun, 23 Feb 2020 04:01:30 +0100 Jan Dankert Refactoring with Namespaces for the cms modules, part 1: moving.