1083afc55SAndreas Gohr<?php 2083afc55SAndreas Gohr 3ba766201SAndreas Gohrnamespace dokuwiki\plugin\struct\meta; 4d486d6d7SAndreas Gohr 5ba766201SAndreas Gohruse dokuwiki\plugin\struct\types\AbstractBaseType; 6083afc55SAndreas Gohr 745560cc7SAndreas Gohrif(!defined('JSON_PRETTY_PRINT')) define('JSON_PRETTY_PRINT', 0); // PHP 5.3 compatibility 845560cc7SAndreas Gohr 97182938bSAndreas Gohr/** 107182938bSAndreas Gohr * Class Schema 117182938bSAndreas Gohr * 127182938bSAndreas Gohr * Represents the schema of a single data table and all its properties. It defines what can be stored in 137182938bSAndreas Gohr * the represented data table and how those contents are formatted. 147182938bSAndreas Gohr * 157182938bSAndreas Gohr * It can be initialized with a timestamp to access the schema as it looked at that particular point in time. 167182938bSAndreas Gohr * 17ba766201SAndreas Gohr * @package dokuwiki\plugin\struct\meta 187182938bSAndreas Gohr */ 19083afc55SAndreas Gohrclass Schema { 20083afc55SAndreas Gohr 21f800af69SMichael Große use TranslationUtilities; 22f800af69SMichael Große 23083afc55SAndreas Gohr /** @var \helper_plugin_sqlite|null */ 24083afc55SAndreas Gohr protected $sqlite; 25083afc55SAndreas Gohr 26083afc55SAndreas Gohr /** @var int The ID of this schema */ 27083afc55SAndreas Gohr protected $id = 0; 28083afc55SAndreas Gohr 29fa7b96aaSMichael Grosse /** @var string the user who last edited this schema */ 30fa7b96aaSMichael Grosse protected $user = ''; 31fa7b96aaSMichael Grosse 32083afc55SAndreas Gohr /** @var string name of the associated table */ 33083afc55SAndreas Gohr protected $table = ''; 34083afc55SAndreas Gohr 3588b7d2aaSAndreas Gohr /** @var bool is this a lookup schema? */ 3688b7d2aaSAndreas Gohr protected $islookup = false; 3788b7d2aaSAndreas Gohr 38083afc55SAndreas Gohr /** 39083afc55SAndreas Gohr * @var string the current checksum of this schema 40083afc55SAndreas Gohr */ 41083afc55SAndreas Gohr protected $chksum = ''; 42083afc55SAndreas Gohr 431c502704SAndreas Gohr /** @var Column[] all the colums */ 44083afc55SAndreas Gohr protected $columns = array(); 45083afc55SAndreas Gohr 46083afc55SAndreas Gohr /** @var int */ 47083afc55SAndreas Gohr protected $maxsort = 0; 48083afc55SAndreas Gohr 49250c83c2SAndreas Gohr /** @var int */ 50250c83c2SAndreas Gohr protected $ts = 0; 51250c83c2SAndreas Gohr 52d486d6d7SAndreas Gohr /** @var string struct version info */ 53d486d6d7SAndreas Gohr protected $structversion = '?'; 54d486d6d7SAndreas Gohr 55127d6bacSMichael Große /** @var array config array with label translations */ 56127d6bacSMichael Große protected $config = array(); 57e2c90eebSAndreas Gohr 58083afc55SAndreas Gohr /** 59083afc55SAndreas Gohr * Schema constructor 607182938bSAndreas Gohr * 61083afc55SAndreas Gohr * @param string $table The table this schema is for 62083afc55SAndreas Gohr * @param int $ts The timestamp for when this schema was valid, 0 for current 6388b7d2aaSAndreas Gohr * @param bool $islookup only used when creating a new schema, makes the new schema a lookup 64083afc55SAndreas Gohr */ 6588b7d2aaSAndreas Gohr public function __construct($table, $ts = 0, $islookup = false) { 66127d6bacSMichael Große $baseconfig = array('allowed editors' => ''); 67127d6bacSMichael Große 68083afc55SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 69083afc55SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 70d486d6d7SAndreas Gohr $info = $helper->getInfo(); 71d486d6d7SAndreas Gohr $this->structversion = $info['date']; 72083afc55SAndreas Gohr $this->sqlite = $helper->getDB(); 73083afc55SAndreas Gohr $table = self::cleanTableName($table); 74083afc55SAndreas Gohr $this->table = $table; 75250c83c2SAndreas Gohr $this->ts = $ts; 76083afc55SAndreas Gohr 77083afc55SAndreas Gohr // load info about the schema itself 78083afc55SAndreas Gohr if($ts) { 79083afc55SAndreas Gohr $sql = "SELECT * 80083afc55SAndreas Gohr FROM schemas 81083afc55SAndreas Gohr WHERE tbl = ? 82083afc55SAndreas Gohr AND ts <= ? 83083afc55SAndreas Gohr ORDER BY ts DESC 84083afc55SAndreas Gohr LIMIT 1"; 85083afc55SAndreas Gohr $opt = array($table, $ts); 86083afc55SAndreas Gohr } else { 87083afc55SAndreas Gohr $sql = "SELECT * 88083afc55SAndreas Gohr FROM schemas 89083afc55SAndreas Gohr WHERE tbl = ? 90083afc55SAndreas Gohr ORDER BY ts DESC 91083afc55SAndreas Gohr LIMIT 1"; 92083afc55SAndreas Gohr $opt = array($table); 93083afc55SAndreas Gohr } 94083afc55SAndreas Gohr $res = $this->sqlite->query($sql, $opt); 95127d6bacSMichael Große $config = array(); 96083afc55SAndreas Gohr if($this->sqlite->res2count($res)) { 974e2abec0SMichael Große $schema = $this->sqlite->res2arr($res); 984e2abec0SMichael Große $result = array_shift($schema); 99083afc55SAndreas Gohr $this->id = $result['id']; 100fa7b96aaSMichael Grosse $this->user = $result['user']; 101083afc55SAndreas Gohr $this->chksum = $result['chksum']; 10288b7d2aaSAndreas Gohr $this->islookup = $result['islookup']; 103587e314dSAndreas Gohr $this->ts = $result['ts']; 104127d6bacSMichael Große $config = json_decode($result['config'], true); 10588b7d2aaSAndreas Gohr } else { 10688b7d2aaSAndreas Gohr $this->islookup = $islookup; 107083afc55SAndreas Gohr } 108083afc55SAndreas Gohr $this->sqlite->res_close($res); 109127d6bacSMichael Große $this->config = array_merge($baseconfig, $config); 110f800af69SMichael Große $this->initTransConfig(array('label')); 111083afc55SAndreas Gohr if(!$this->id) return; 112083afc55SAndreas Gohr 113083afc55SAndreas Gohr // load existing columns 114083afc55SAndreas Gohr $sql = "SELECT SC.*, T.* 115083afc55SAndreas Gohr FROM schema_cols SC, 116083afc55SAndreas Gohr types T 1171c502704SAndreas Gohr WHERE SC.sid = ? 1181c502704SAndreas Gohr AND SC.tid = T.id 119083afc55SAndreas Gohr ORDER BY SC.sort"; 1201c502704SAndreas Gohr $res = $this->sqlite->query($sql, $this->id); 121083afc55SAndreas Gohr $rows = $this->sqlite->res2arr($res); 122083afc55SAndreas Gohr $this->sqlite->res_close($res); 123083afc55SAndreas Gohr 124636c8abaSAndreas Gohr $typeclasses = Column::allTypes(); 125083afc55SAndreas Gohr foreach($rows as $row) { 126328db41bSAndreas Gohr if($row['class'] == 'Integer') { 127328db41bSAndreas Gohr $row['class'] = 'Decimal'; 128328db41bSAndreas Gohr } 129328db41bSAndreas Gohr 130636c8abaSAndreas Gohr $class = $typeclasses[$row['class']]; 13198eaa57dSAndreas Gohr if(!class_exists($class)) { 13298eaa57dSAndreas Gohr // This usually never happens, except during development 13398eaa57dSAndreas Gohr msg('Unknown type "' . hsc($row['class']) . '" falling back to Text', -1); 134ba766201SAndreas Gohr $class = 'dokuwiki\\plugin\\struct\\types\\Text'; 13598eaa57dSAndreas Gohr } 13698eaa57dSAndreas Gohr 137083afc55SAndreas Gohr $config = json_decode($row['config'], true); 138bbf3d6aaSAndreas Gohr /** @var AbstractBaseType $type */ 139bbf3d6aaSAndreas Gohr $type = new $class($config, $row['label'], $row['ismulti'], $row['tid']); 140bbf3d6aaSAndreas Gohr $column = new Column( 1411c502704SAndreas Gohr $row['sort'], 142bbf3d6aaSAndreas Gohr $type, 1431c502704SAndreas Gohr $row['colref'], 14463d51bbfSAndreas Gohr $row['enabled'], 14563d51bbfSAndreas Gohr $table 1461c502704SAndreas Gohr ); 147bbf3d6aaSAndreas Gohr $type->setContext($column); 1481c502704SAndreas Gohr 1497629557eSAndreas Gohr $this->columns[] = $column; 150083afc55SAndreas Gohr if($row['sort'] > $this->maxsort) $this->maxsort = $row['sort']; 151083afc55SAndreas Gohr } 152083afc55SAndreas Gohr } 153083afc55SAndreas Gohr 154083afc55SAndreas Gohr /** 15567641668SAndreas Gohr * @return string identifer for debugging purposes 15667641668SAndreas Gohr */ 15767641668SAndreas Gohr function __toString() { 15867641668SAndreas Gohr return __CLASS__ . ' ' . $this->table . ' (' . $this->id . ') ' . ($this->islookup ? 'LOOKUP' : 'DATA'); 15967641668SAndreas Gohr } 16067641668SAndreas Gohr 16167641668SAndreas Gohr /** 162083afc55SAndreas Gohr * Cleans any unwanted stuff from table names 163083afc55SAndreas Gohr * 164083afc55SAndreas Gohr * @param string $table 165083afc55SAndreas Gohr * @return string 166083afc55SAndreas Gohr */ 167083afc55SAndreas Gohr static public function cleanTableName($table) { 1682af472dcSAndreas Gohr $table = strtolower($table); 169083afc55SAndreas Gohr $table = preg_replace('/[^a-z0-9_]+/', '', $table); 170083afc55SAndreas Gohr $table = preg_replace('/^[0-9_]+/', '', $table); 171083afc55SAndreas Gohr $table = trim($table); 172083afc55SAndreas Gohr return $table; 173083afc55SAndreas Gohr } 174083afc55SAndreas Gohr 175127d6bacSMichael Große 176127d6bacSMichael Große /** 177097f4a53SAndreas Gohr * Gets a list of all available schemas 178097f4a53SAndreas Gohr * 1797c080d69SAndreas Gohr * @param string $filter either 'page' or 'lookup' 1807c080d69SAndreas Gohr * @return \string[] 181097f4a53SAndreas Gohr */ 1827c080d69SAndreas Gohr static public function getAll($filter = '') { 183097f4a53SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 184097f4a53SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 1857cbcfbdbSAndreas Gohr $db = $helper->getDB(false); 186097f4a53SAndreas Gohr if(!$db) return array(); 187097f4a53SAndreas Gohr 1887c080d69SAndreas Gohr if($filter == 'page') { 1897c080d69SAndreas Gohr $where = 'islookup = 0'; 1907c080d69SAndreas Gohr } elseif($filter == 'lookup') { 1917c080d69SAndreas Gohr $where = 'islookup = 1'; 1927c080d69SAndreas Gohr } else { 1937c080d69SAndreas Gohr $where = '1 = 1'; 1947c080d69SAndreas Gohr } 1957c080d69SAndreas Gohr 1967c080d69SAndreas Gohr $res = $db->query("SELECT DISTINCT tbl FROM schemas WHERE $where ORDER BY tbl"); 197097f4a53SAndreas Gohr $tables = $db->res2arr($res); 198097f4a53SAndreas Gohr $db->res_close($res); 199097f4a53SAndreas Gohr 200097f4a53SAndreas Gohr $result = array(); 201097f4a53SAndreas Gohr foreach($tables as $row) { 202097f4a53SAndreas Gohr $result[] = $row['tbl']; 203097f4a53SAndreas Gohr } 204097f4a53SAndreas Gohr return $result; 205097f4a53SAndreas Gohr } 206097f4a53SAndreas Gohr 207097f4a53SAndreas Gohr /** 208d5a1a6dcSAndreas Gohr * Delete all data associated with this schema 209d5a1a6dcSAndreas Gohr * 210d5a1a6dcSAndreas Gohr * This is really all data ever! Be careful! 211d5a1a6dcSAndreas Gohr */ 212d5a1a6dcSAndreas Gohr public function delete() { 213d5a1a6dcSAndreas Gohr if(!$this->id) throw new StructException('can not delete unsaved schema'); 214d5a1a6dcSAndreas Gohr 215d5a1a6dcSAndreas Gohr $this->sqlite->query('BEGIN TRANSACTION'); 216d5a1a6dcSAndreas Gohr 217d5a1a6dcSAndreas Gohr $sql = "DROP TABLE ?"; 218d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'data_' . $this->table); 219d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'multi_' . $this->table); 220d5a1a6dcSAndreas Gohr 221d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments WHERE tbl = ?"; 222d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 223d5a1a6dcSAndreas Gohr 224d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments_patterns WHERE tbl = ?"; 225d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 226d5a1a6dcSAndreas Gohr 227d5a1a6dcSAndreas Gohr $sql = "SELECT T.id 228d5a1a6dcSAndreas Gohr FROM types T, schema_cols SC, schemas S 229d5a1a6dcSAndreas Gohr WHERE T.id = SC.tid 230d5a1a6dcSAndreas Gohr AND SC.sid = S.id 231d5a1a6dcSAndreas Gohr AND S.tbl = ?"; 232d5a1a6dcSAndreas Gohr $sql = "DELETE FROM types WHERE id IN ($sql)"; 233d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 234d5a1a6dcSAndreas Gohr 235d5a1a6dcSAndreas Gohr $sql = "SELECT id 236d5a1a6dcSAndreas Gohr FROM schemas 237d5a1a6dcSAndreas Gohr WHERE tbl = ?"; 238d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_cols WHERE sid IN ($sql)"; 239d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 240d5a1a6dcSAndreas Gohr 241d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schemas WHERE tbl = ?"; 242d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 243d5a1a6dcSAndreas Gohr 244d5a1a6dcSAndreas Gohr $this->sqlite->query('COMMIT TRANSACTION'); 245d5a1a6dcSAndreas Gohr $this->sqlite->query('VACUUM'); 246f9f13d8cSAndreas Gohr 247f9f13d8cSAndreas Gohr // a deleted schema should not be used anymore, but let's make sure it's somewhat sane anyway 248f9f13d8cSAndreas Gohr $this->id = 0; 249f9f13d8cSAndreas Gohr $this->chksum = ''; 250f9f13d8cSAndreas Gohr $this->columns = array(); 251f9f13d8cSAndreas Gohr $this->maxsort = 0; 252f9f13d8cSAndreas Gohr $this->ts = 0; 253d5a1a6dcSAndreas Gohr } 254d5a1a6dcSAndreas Gohr 255*79c83e06SMichael Große 256*79c83e06SMichael Große /** 257*79c83e06SMichael Große * Clear all data of a schema, but retain the schema itself 258*79c83e06SMichael Große */ 259*79c83e06SMichael Große public function clear() { 260*79c83e06SMichael Große if(!$this->id) throw new StructException('can not clear data of unsaved schema'); 261*79c83e06SMichael Große 262*79c83e06SMichael Große $this->sqlite->query('BEGIN TRANSACTION'); 263*79c83e06SMichael Große $sql = 'DELETE FROM ?'; 264*79c83e06SMichael Große $this->sqlite->query($sql, 'data_' . $this->table); 265*79c83e06SMichael Große $this->sqlite->query($sql, 'multi_' . $this->table); 266*79c83e06SMichael Große $this->sqlite->query('COMMIT TRANSACTION'); 267*79c83e06SMichael Große $this->sqlite->query('VACUUM'); 268*79c83e06SMichael Große } 269*79c83e06SMichael Große 270d5a1a6dcSAndreas Gohr /** 2711c502704SAndreas Gohr * @return string 2721c502704SAndreas Gohr */ 2731c502704SAndreas Gohr public function getChksum() { 2741c502704SAndreas Gohr return $this->chksum; 2751c502704SAndreas Gohr } 2761c502704SAndreas Gohr 2771c502704SAndreas Gohr /** 2781c502704SAndreas Gohr * @return int 2791c502704SAndreas Gohr */ 2801c502704SAndreas Gohr public function getId() { 2811c502704SAndreas Gohr return $this->id; 2821c502704SAndreas Gohr } 2831c502704SAndreas Gohr 2841c502704SAndreas Gohr /** 285587e314dSAndreas Gohr * @return int returns the timestamp this Schema was created at 286f411d872SAndreas Gohr */ 287f411d872SAndreas Gohr public function getTimeStamp() { 288f411d872SAndreas Gohr return $this->ts; 289f411d872SAndreas Gohr } 290f411d872SAndreas Gohr 291f411d872SAndreas Gohr /** 29288b7d2aaSAndreas Gohr * @return bool is this a lookup schema? 29388b7d2aaSAndreas Gohr */ 29488b7d2aaSAndreas Gohr public function isLookup() { 29588b7d2aaSAndreas Gohr return $this->islookup; 29688b7d2aaSAndreas Gohr } 29788b7d2aaSAndreas Gohr 29888b7d2aaSAndreas Gohr /** 299fa7b96aaSMichael Grosse * @return string 300fa7b96aaSMichael Grosse */ 301fa7b96aaSMichael Grosse public function getUser() { 302fa7b96aaSMichael Grosse return $this->user; 303fa7b96aaSMichael Grosse } 304fa7b96aaSMichael Grosse 305127d6bacSMichael Große public function getConfig() { 306127d6bacSMichael Große return $this->config; 307e2c90eebSAndreas Gohr } 308e2c90eebSAndreas Gohr 309fa7b96aaSMichael Grosse /** 310f800af69SMichael Große * Returns the translated label for this schema 311f800af69SMichael Große * 312f800af69SMichael Große * Uses the current language as determined by $conf['lang']. Falls back to english 313f800af69SMichael Große * and then to the Schema label 314f800af69SMichael Große * 315f800af69SMichael Große * @return string 316f800af69SMichael Große */ 317f800af69SMichael Große public function getTranslatedLabel() { 318f800af69SMichael Große return $this->getTranslatedKey('label', $this->table); 319f800af69SMichael Große } 320f800af69SMichael Große 321f800af69SMichael Große /** 3226ebbbb8eSAndreas Gohr * Checks if the current user may edit data in this schema 3236ebbbb8eSAndreas Gohr * 3246ebbbb8eSAndreas Gohr * @return bool 3256ebbbb8eSAndreas Gohr */ 3266ebbbb8eSAndreas Gohr public function isEditable() { 3276ebbbb8eSAndreas Gohr global $USERINFO; 328127d6bacSMichael Große if($this->config['allowed editors'] === '') return true; 3296ebbbb8eSAndreas Gohr if(blank($_SERVER['REMOTE_USER'])) return false; 3306ebbbb8eSAndreas Gohr if(auth_isadmin()) return true; 331127d6bacSMichael Große return auth_isMember($this->config['allowed editors'], $_SERVER['REMOTE_USER'], $USERINFO['grps']); 3326ebbbb8eSAndreas Gohr } 3336ebbbb8eSAndreas Gohr 3346ebbbb8eSAndreas Gohr /** 335ce206ec7SAndreas Gohr * Returns a list of columns in this schema 336ce206ec7SAndreas Gohr * 337ce206ec7SAndreas Gohr * @param bool $withDisabled if false, disabled columns will not be returned 338ce206ec7SAndreas Gohr * @return Column[] 3391c502704SAndreas Gohr */ 340ce206ec7SAndreas Gohr public function getColumns($withDisabled = true) { 341ce206ec7SAndreas Gohr if(!$withDisabled) { 342ce206ec7SAndreas Gohr return array_filter( 343ce206ec7SAndreas Gohr $this->columns, 344ce206ec7SAndreas Gohr function (Column $col) { 345ce206ec7SAndreas Gohr return $col->isEnabled(); 346ce206ec7SAndreas Gohr } 347ce206ec7SAndreas Gohr ); 348ce206ec7SAndreas Gohr } 349ce206ec7SAndreas Gohr 3501c502704SAndreas Gohr return $this->columns; 3511c502704SAndreas Gohr } 3521c502704SAndreas Gohr 353ae697e1fSAndreas Gohr /** 3545742aea9SAndreas Gohr * Find a column in the schema by its label 3555742aea9SAndreas Gohr * 3565742aea9SAndreas Gohr * Only enabled columns are returned! 3575742aea9SAndreas Gohr * 3585742aea9SAndreas Gohr * @param $name 3595742aea9SAndreas Gohr * @return bool|Column 3605742aea9SAndreas Gohr */ 3615742aea9SAndreas Gohr public function findColumn($name) { 3625742aea9SAndreas Gohr foreach($this->columns as $col) { 3635742aea9SAndreas Gohr if($col->isEnabled() && utf8_strtolower($col->getLabel()) == utf8_strtolower($name)) { 3645742aea9SAndreas Gohr return $col; 3655742aea9SAndreas Gohr } 3665742aea9SAndreas Gohr } 3675742aea9SAndreas Gohr return false; 3685742aea9SAndreas Gohr } 3695742aea9SAndreas Gohr 3705742aea9SAndreas Gohr /** 371ae697e1fSAndreas Gohr * @return string 372ae697e1fSAndreas Gohr */ 373ae697e1fSAndreas Gohr public function getTable() { 374ae697e1fSAndreas Gohr return $this->table; 375ae697e1fSAndreas Gohr } 3761c502704SAndreas Gohr 377ae697e1fSAndreas Gohr /** 378ae697e1fSAndreas Gohr * @return int the highest sort number used in this schema 379ae697e1fSAndreas Gohr */ 380ae697e1fSAndreas Gohr public function getMaxsort() { 381ae697e1fSAndreas Gohr return $this->maxsort; 382ae697e1fSAndreas Gohr } 3831c502704SAndreas Gohr 384d486d6d7SAndreas Gohr /** 385d486d6d7SAndreas Gohr * @return string the JSON representing this schema 386d486d6d7SAndreas Gohr */ 387d486d6d7SAndreas Gohr public function toJSON() { 388d486d6d7SAndreas Gohr $data = array( 389d486d6d7SAndreas Gohr 'structversion' => $this->structversion, 390d486d6d7SAndreas Gohr 'schema' => $this->getTable(), 391d486d6d7SAndreas Gohr 'id' => $this->getId(), 39245313413SMichael Grosse 'user' => $this->getUser(), 393127d6bacSMichael Große 'config' => $this->getConfig(), 394d486d6d7SAndreas Gohr 'columns' => array() 395d486d6d7SAndreas Gohr ); 396d486d6d7SAndreas Gohr 397d486d6d7SAndreas Gohr foreach($this->columns as $column) { 398d486d6d7SAndreas Gohr $data['columns'][] = array( 399d486d6d7SAndreas Gohr 'colref' => $column->getColref(), 400d486d6d7SAndreas Gohr 'ismulti' => $column->isMulti(), 401d486d6d7SAndreas Gohr 'isenabled' => $column->isEnabled(), 402d486d6d7SAndreas Gohr 'sort' => $column->getSort(), 403d486d6d7SAndreas Gohr 'label' => $column->getLabel(), 404d486d6d7SAndreas Gohr 'class' => $column->getType()->getClass(), 405d486d6d7SAndreas Gohr 'config' => $column->getType()->getConfig(), 406d486d6d7SAndreas Gohr ); 407d486d6d7SAndreas Gohr } 408d486d6d7SAndreas Gohr 409d486d6d7SAndreas Gohr return json_encode($data, JSON_PRETTY_PRINT); 410d486d6d7SAndreas Gohr } 411083afc55SAndreas Gohr} 412