tra('Document Preferences'), 'documentation' => 'PluginPrefDoc', 'description' => tra('Generate documentation for Tiki preference tabs, mostly for use on the Tiki documentation website (doc.tiki.org).'), 'prefs' => [ 'wikiplugin_prefdoc' ], 'validate' => 'all', 'introduced' => 17, 'iconname' => 'th-list', 'tags' => [ 'advanced' ], 'params' => [ 'tab' => [ 'required' => false, 'name' => tra('Preference-Tab'), 'description' => tra('The name of the preference tab to display, or a list of available tabs upon invalid.'), 'filter' => 'text', 'since' => '17', ], 'img' => [ 'required' => false, 'name' => tra('Images'), 'description' => tra('Show images at the top of each version of Tiki. Format: TikiVersion:FileGalleryID. Multiple images my be separated like so: TikiVersion1:FileGalleryID1|TikiVersion2:FileGalleryID2.'), 'since' => '17', 'filter' => 'text', ], ], ]; } /** * * Generate, Display & Archive Tiki Pref Documentation * * @param $data string this is ignored * @param $params array parameters to process * * @return string formatted pref table (wiki format) or list of preference tabs (with errors) */ function wikiplugin_prefdoc($data, $params) { $Doc = new PrefsDoc(); if (is_readable('storage/prefsdoc/state.json')) { $Doc->state = json_decode(file_get_contents("storage/prefsdoc/state.json")); } if (! $Doc->genPrefsState()) { return $Doc->error; } if (! isset($params['tab'])) { // if no tab specified, return a list of available tabs return $Doc->genPrefCodes(); } if (! $Doc->genPrefHistory($params['tab'], @$params['img'])) { return $Doc->error; } return $Doc->error . $Doc->docTable; } /** * * * Used for automatically generating preference documentation * for use on doc.tiki.org. It takes the currently installed preferences * and generates files in wiki-syntax * * Running this will overwrite any previously generated pref doc files * * * Class PrefsDoc * * @var $docTable string The output to be sent to displayed, in wiki format * */ class PrefsDoc extends TWVersion { public $docTable; public $error; public $state; private $PrefVars; private $prevFilePrefs; private $fileCount; private $prefCount; private $prefDefault; private $prefDefaultFull; private $prefDescription; private $prefName; /** * * Populates $docTable with a string to display. * The main function to generate version tabs etc. * * @param $tabName string The pref-tab name to create an output for * @param $images string Images & Versions to display with a Version tab. * * @return bool true on success false on failure */ public function genPrefHistory($tabName, $images) { if (! isset($this->state->files->{$tabName})) { $this->error .= "Error: Cant find $tabName you may choose from one of the following: \n" . $this->genPrefCodes(); return false; } $this->docTable = '{TABS(name="' . $tabName . '" tabs="Tiki Version ' . implode('|', $this->state->files->{$tabName}) . "\" toggle=\"n\")}"; $imageArray = []; $images = explode('|', $images); foreach ($images as $key => $image) { $image = explode(':', $image); $imageArray[$image[0]][0] = @$image[1]; $imageArray[$image[0]][1] = $image[0]; } unset($images); // carry over images from past versions of Tiki, if available. foreach ($this->state->files->{$tabName} as $key => $version) { $count = $version; while ($version - $count < 5) { // If the image is older than 5 tiki versions, dont display it. if (@$imageArray[$count][0]) { $this->docTable .= '{img fileId="' . $imageArray[$count][0] . '" thumb="y" rel="box[g]" width="300" desc="Tiki ' . $imageArray[$count][1] . ' Preferences Image" alt="' . $tabName . '" align="center"}'; break; } $count--; } $this->genPrefVersion("storage/prefsdoc/$version-$tabName.json"); $key++; if (isset($this->state->files->{$tabName}[$key])) { $this->docTable .= '/////'; } } $this->docTable .= '{TABS}'; return true; } /** * * Generates a list of valid pref codes when no preference page was specified * * @return string file-tab codes available for use. */ public function genPrefCodes() { $codes = ''; foreach ($this->state->files as $key => $value) { $codes .= $key . '
'; } return $codes; } /** * * Generates a wiki syntax table for a specific version of tiki * * @param string $fileName The file name to write to disk. * * @return bool false on error, true otherwise */ private function genPrefVersion($fileName) { if (! is_file($fileName)) { $this->error .= "Cant read $fileName. "; return false; } $this->docTable .= '{FANCYTABLE(head="Option | Description | Default" sortable="n")}'; $FilePrefs = json_decode(file_get_contents($fileName)); foreach ($FilePrefs->prefs as $prefName => &$pref) { $this->prevFilePrefs; // carry over missing information filled out in a newer version if (empty($pref->description)) { $pref->description = @$this->prevFilePrefs->$prefName->description; } if (empty($pref->detail)) { $pref->detail = @$this->prevFilePrefs->$prefName->detail; } if (empty($pref->help)) { $pref->help = @$this->prevFilePrefs->$prefName->help; } if (empty($pref->hint)) { $pref->hint = @$this->prevFilePrefs->$prefName->hint; } if (empty($pref->shorthint)) { $pref->shorthint = @$this->prevFilePrefs->$prefName->shorthint; } if (empty($pref->warning)) { $pref->warning = @$this->prevFilePrefs->$prefName->warning; } $this->setParams($pref); $this->docTable .= $this->prefName . '~|~' . $this->prefDescription . '~|~' . $this->prefDefault . "\n"; } $this->prevFilePrefs = $FilePrefs->prefs; $this->docTable .= "{FANCYTABLE}"; return true; } /** * * This sets an vars of an individual preference * * @param $param object the prefs to be processed into pretty & standardized output */ private function setParams($param) { $this->prefDefaultFull = ''; // set default if (! empty($param->options) && isset($param->default) && $param->default !== '') { $this->prefDefault = $param->options->{$param->default}; } elseif ($param->default === 'n') { $this->prefDefault = 'Disabled'; } elseif ($param->default === 'y') { $this->prefDefault = 'Enabled'; // Change default codes to human readable format } elseif (is_array($param->default)) { $this->prefDefault = implode(', ', $param->default); } else { $this->prefDefault = $param->default; } // end first processing the below should be applied to the above.... not a continuation (eg. empty array) $this->prefDefault = trim($this->prefDefault); if ($this->prefDefault == '') { $this->prefDefault = '~~gray:None~~'; } elseif (! empty($param->units)) { $this->prefDefault .= ' ' . $param->units; } elseif (! preg_match('/\W/', $this->prefDefault)) { // if Pref is a singe word $this->prefDefault = ucfirst($this->prefDefault); // then caps the first letter. } else { if (strlen($this->prefDefault) > 30) { $this->prefDefaultFull = $this->wikiConvert($this->prefDefault, true); $this->prefDefault = substr($this->prefDefault, 0, 27) . '...'; } $this->prefDefault = $this->wikiConvert($this->prefDefault, true); } // set name if ($param->help) { $this->prefName = '~np~' . $param->name . '~/np~'; } else { $this->prefName = '~np~' . $param->name . '~/np~'; } $this->prefName = $this->wikiConvert($this->prefName); // set description $this->prefDescription = $param->description; if ($param->detail) { if ($this->prefDescription) { // new line if existing content $this->prefDescription .= '
'; } $this->prefDescription .= ' ' . $param->detail . ''; } if ($param->hint) { if ($this->prefDescription) { // new line if existing content $this->prefDescription .= '
'; } $this->prefDescription .= ' ' . $param->hint . ''; } if ($param->shorthint) { if ($this->prefDescription) { // new line if existing content $this->prefDescription .= '
'; } $this->prefDescription .= ' ' . $param->shorthint . ''; } // if the pref is marked as deprecated, show as such and use the warning as the deprecated notice. if (! empty($param->tags)) { foreach ($param->tags as $tag) { if ($tag === 'deprecated') { if (! $param->warning) { $param->warning = 'Will be removed in an upcoming version of Tiki.'; } $this->prefDescription .= ' '; unset($param->warning); } } } if ($param->warning) { if ($this->prefDescription) { // new line if existing content $this->prefDescription .= '
'; } $this->prefDescription .= ' ' . $param->warning . ''; } // display list of options if (! empty($param->options)) { $count = 0; $options = ''; foreach ($param->options as $option) { if ($count) { $options .= ' | '; } $options .= $option; $count++; } if ($count) { // If options exist, then add them if ($this->prefDescription) { // new line if existing content $this->prefDescription .= '
'; if (strlen($options) > 400) { // truncate options if they get too long $options = substr($options, 0, 397) . '...'; } } $options = $this->wikiConvert($options, true); // sanitize special characters $options = preg_replace('/\s+/', ' ', $options); // replace all excess whitespace characters with a single space. $this->prefDescription .= ' ' . $options . ''; } } if (! empty($param->tags)) { foreach ($param->tags as $tag) { if ($tag === 'experimental') { $this->prefDescription .= ' '; } } } $this->prefDescription = $this->wikiConvert($this->prefDescription); } /** * Preps a string from tiki prefs for insertion into tiki-syntax land * * @param $string string to be parsed * * @param $escape bool if $string should be enclosed in tiki no-parse tags * * * @return string parsed string sutable for wiki insertion * */ private function wikiConvert($string, $escape = false) { $escapedString = ''; if ($string) { if ($escape) { $escapedString = ' ~np~'; } $escapedString .= str_replace("\n", ' ', $string); $escapedString = trim($escapedString); if ($escape) { $escapedString .= '~/np~'; } } return $escapedString; } /** * * Check if the prefs documentation is up to date. * If its not up to date, then update it. * * Documentation is updated for every minor version change. * A separate set of documentation is created for every major version. * Only consecutive versions are supported (you cant skip a version) * * @return bool false on error, true on success. */ public function genPrefsState() { if ($this->state->version === $this->getBaseVersion()) { return true; } if (! is_dir('storage/prefsdoc')) { if (! mkdir('storage/prefsdoc')) { // create subdir for housing generated files, if it does not exist $this->error .= "Cant create storage/prefsdoc directory."; return false; } } // prepare to generate prefs doc $this->fileCount = 0; $this->prefCount = 0; $this->getPrefs(); $docFiles = scandir('templates/admin'); // grab all the files that house prefs foreach ($docFiles as $fileName) { if (substr($fileName, 0, 8) === 'include_') { // filter out any file thats not a pref file $FilePrefs = $this->getAdminUIPrefs($fileName); foreach ($FilePrefs as $tabName => $tab) { if (! $this->writeFile($tabName, $tab)) { return false; } $this->prefCount++; } } } // record the state so its easy to figure out whats what next time. $preState = (object)[]; $files = scandir('storage/prefsdoc/'); foreach ($files as $file) { if (preg_match('/^([\d]+)-([a-z0-9-]+).json$/', $file, $matches)) { // return version number and tab name, filtering out non-matching files $preState->{$matches[2]}[] = (int)$matches[1]; } } foreach ($preState as $key => $tabState) { // sort the versions from high to low rsort($preState->$key); } $this->state = json_encode([ 'version' => $this->getBaseVersion(), 'created' => time(), 'files' => $preState]); file_put_contents('storage/prefsdoc/state.json', $this->state); $this->state = json_decode($this->state); $logslib = TikiLib::lib('logs'); $logslib->add_log('prefDoc', $this->fileCount . ' pref doc files generated/updated covering ' . $this->prefCount . ' prefs'); return true; } /** * compiles a all prefs for the current tiki install and sets PrefVars with it */ private function getPrefs() { $prefs = []; $docFiles = scandir('lib/prefs'); // grab all the files that house prefs foreach ($docFiles as $fileName) { if ($fileName !== 'index.php' && substr($fileName, -4) === '.php') { // filter out any file thats not a pref file require_once('lib/prefs/' . $fileName); $callVar = 'prefs_' . substr($fileName, 0, -4) . '_list'; $prefs = array_merge($prefs, $callVar()); // create one big var with all the pref info } } // Sanitise specific output $prefs['webcron_token']['default'] = 'Random Token'; $this->PrefVars = $prefs; } /** * * This generates a list of prefs in the order that they appear on the admin panel. * * @param string $fileName Name of file to scan * * @return array array of pref names, or false on failure * */ private function getAdminUIPrefs($fileName) { $file = file_get_contents('templates/admin/' . $fileName); $fileName = substr(substr($fileName, 8), 0, -4); // prepare the file name for further use $count = preg_match_all('/{tab name="?\'?(?:{tr})?([\w\s]*)(?:{\/tr})?"?\'?.*?}([\w\W]*?){\/tab}/i', $file, $tabs); if ($count) { while ($count >= 1) { $count--; $prefs = []; preg_match_all('/{preference.*name="?\'?(\w*)"?\'?.*}/i', $tabs[2][$count], $prefs); // Generate array of all the prefs $tabs[1][$count] = mb_ereg_replace('\W', '', strtolower($tabs[1][$count])); // sanitize the tab name for disk foreach ($prefs[1] as $pref) { if ($this->PrefVars[$pref]['name']) { // dont save prefs that have no name $tabPrefs[$fileName . '-' . $tabs[1][$count]][$pref] = $this->PrefVars[$pref]; // Add full pref info in right order } } } } elseif (preg_match_all('/{preference.*name="?\'?(\w*)"?\'?.*}/i', $file, $prefs)) { foreach ($prefs[1] as $pref) { $tabPrefs[$fileName][$pref] = $this->PrefVars[$pref]; // Add full pref info in right order } } return $tabPrefs; } /** * * Writes a pref file to disk * * @param $tabName string name of tab or pref file to write * @param $prefs array the prefs to write to file * * @return bool|string returns error message on failure, or false on success */ private function writeFile($tabName, $prefs) { $version = (int)$this->getBaseVersion(); $tabName = '-' . $tabName . '.json'; // Name of file to be written, minus prefex $prefs = json_encode([ 'prefs' => $prefs, ]); if (is_file('storage/prefsdoc/' . $version . $tabName)) { if (! unlink('storage/prefsdoc/' . $version . $tabName)) { $this->error .= ("Cant overwrite existing $version-$tabName.json "); return false; } } if (! file_put_contents('storage/prefsdoc/' . $version . $tabName, $prefs)) { // write one file for each pref page on control panel $this->error .= ("Unable to write $version.$tabName"); return false; } $this->fileCount++; return true; } }