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;
}
}