<?php
|
|
|
|
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
|
|
//
|
|
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
|
|
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
|
|
// $Id$
|
|
|
|
function wikiplugin_fluidgrid_info()
|
|
{
|
|
return [
|
|
'name' => tra('Fluid Grid'),
|
|
'documentation' => 'PluginFluidGrid',
|
|
'description' => tra('Arrange content into rows and columns using a Bootstrap fluid grid.'),
|
|
'prefs' => [ 'wikiplugin_fluidgrid' ],
|
|
'body' => tra('Text to display in a grid. Use "---" to separate the columns and "@@@" to separate rows.'),
|
|
'filter' => 'wikicontent',
|
|
'format' => 'html',
|
|
'iconname' => 'table',
|
|
'introduced' => 17,
|
|
'tags' => [ 'basic' ],
|
|
'params' => [
|
|
'joincols' => [
|
|
'required' => false,
|
|
'name' => tra('Join Columns'),
|
|
'description' => tra('Merge empty cells into the cell to their left'),
|
|
'since' => '17',
|
|
'filter' => 'alpha',
|
|
'default' => 'y',
|
|
'options' => [
|
|
['text' => '', 'value' => ''],
|
|
['text' => tra('Yes'), 'value' => 'y'],
|
|
['text' => tra('No'), 'value' => 'n']
|
|
]
|
|
],
|
|
'devicesize' => [
|
|
'required' => false,
|
|
'name' => tra('Device size'),
|
|
'description' => tra('Specify the device size below which the cells will be stacked vertically'),
|
|
'since' => '17',
|
|
'filter' => 'alpha',
|
|
'default' => 'sm',
|
|
'options' => [
|
|
['text' => '', 'value' => ''],
|
|
['text' => tra('Small'), 'value' => 'sm'],
|
|
['text' => tra('Medium'), 'value' => 'md'],
|
|
['text' => tra('Large'), 'value' => 'lg'],
|
|
['text' => tra('Extra Large'), 'value' => 'xl']
|
|
]
|
|
],
|
|
'colsize' => [
|
|
'required' => false,
|
|
'name' => tra('Column Sizes'),
|
|
'description' => tra('Specify all column widths in units which add up to 12'),
|
|
'since' => '17',
|
|
'seprator' => '|',
|
|
'filter' => 'text',
|
|
'default' => '',
|
|
],
|
|
'first' => [
|
|
'required' => false,
|
|
'name' => tra('First'),
|
|
'description' => tra('Cells specified are ordered first left to right across rows (default) or top to bottom down columns'),
|
|
'since' => '17',
|
|
'filter' => 'alpha',
|
|
'default' => 'line',
|
|
'options' => [
|
|
['text' => '', 'value' => ''],
|
|
['text' => tra('Column'), 'value' => 'col'],
|
|
['text' => tra('Line'), 'value' => 'line']
|
|
]
|
|
],
|
|
'customclass' => [
|
|
'required' => false,
|
|
'name' => tra('Custom Class'),
|
|
'description' => tra('Add a class to customize the design'),
|
|
'since' => '17',
|
|
'filter' => 'text',
|
|
'default' => '',
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
//
|
|
// The plugin function starts by removing a list of other plugins, nested
|
|
// inside this one. This function patches them back in.
|
|
//
|
|
function wikiplugin_fluidgrid_rollback($data, $hashes)
|
|
{
|
|
foreach ($hashes as $hash => $match) {
|
|
$data = str_replace($hash, $match, $data);
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/*
|
|
* \note This plugin should carefuly change text it have to parse
|
|
* because some of wiki syntaxes are sensitive for
|
|
* start of new line ('\n' character - e.g. lists and headers)... such
|
|
* user lines must stay with the same layout when applying
|
|
* this plugin to render them properly after...
|
|
* $data = the preparsed data (plugin, code, np.... already parsed)
|
|
* $pos is the position in the object where the non-parsed data begins
|
|
*/
|
|
function wikiplugin_fluidgrid($data, $params, $pos)
|
|
{
|
|
global $tikilib;
|
|
|
|
//
|
|
// The following function uses a regular expression in the form
|
|
// "/pattern/ismU"
|
|
// where / is used as a delimiter and ismU are pattern modifiers
|
|
// i = case insensitive
|
|
// s = dot matches anything (including new line)
|
|
// m = multiline
|
|
// U = Ungreedy
|
|
//
|
|
// The regular expression matches a list of specific plugins. The following
|
|
// loop replaces the plugin with a hash, so that it is excluded from the
|
|
// processing.
|
|
//
|
|
// Question:
|
|
// Ungreedy matching prevents us spanning multiple instances of a plugin,
|
|
// e.g. {SPIIT()}...{SPLIT}...{SPIIT()}...{SPLIT}
|
|
// Will it handle a second level of nested plugins of the same type,
|
|
// e.g. {SPIIT()}...{SPLIT()}...{SPIIT}...{SPLIT}
|
|
//
|
|
preg_match_all('/{(FLUIDGRID|SPLIT|CODE|HTML|FADE|JQ|JS|MOUSEOVER|VERSIONS).+{\1}/ismU', $data, $matches);
|
|
$hashes = [];
|
|
foreach ($matches[0] as $match) {
|
|
if (empty($match)) {
|
|
continue;
|
|
}
|
|
$hash = md5($match);
|
|
$hashes[$hash] = $match;
|
|
$data = str_replace($match, $hash, $data);
|
|
}
|
|
|
|
// Remove first <ENTER> if exists...
|
|
// it may be here if present after {FLUIDGRID()} in original text
|
|
if (substr($data, 0, 2) == "\r\n") {
|
|
$data2 = substr($data, 2);
|
|
} else {
|
|
$data2 = $data;
|
|
}
|
|
|
|
extract($params, EXTR_SKIP);
|
|
$joincols = (! isset($joincols) || $joincols == 'y' || $joincols == 1 ? true : false);
|
|
|
|
// Check the device size parameter which must be one of 'sm', 'md', 'lg' or 'xl'
|
|
if (! isset($devicesize) || ! ( ( $devicesize == 'sm' ) || ( $devicesize == 'md' ) || ( $devicesize == 'lg' ) || ( $devicesize == 'xl' ) )) {
|
|
$devicesize = 'sm' ;
|
|
}
|
|
|
|
// Split data by rows and cells
|
|
$sections = preg_split("/@@@+/", $data2);
|
|
$rows = [];
|
|
$maxcols = 0;
|
|
foreach ($sections as $i) {
|
|
// split by --- but not by ----
|
|
// $rows[] = preg_split("/([^\-]---[^\-]|^---[^\-]|[^\-]---$|^---$)+/", $i);
|
|
// not to eat the character close to - and to split on --- and not ----
|
|
$rows[] = preg_split("/(?<!-)---(?!-)/", $i);
|
|
$maxcols = max($maxcols, count(end($rows)));
|
|
}
|
|
|
|
// Are there any sections present?
|
|
// Do not touch anything if not... don't even generate <table>
|
|
if (count($rows) <= 1 && count($rows[0]) <= 1) {
|
|
return wikiplugin_fluidgrid_rollback($data, $hashes);
|
|
}
|
|
|
|
//
|
|
// The "first" parameter indicates whether the content is listed in columns
|
|
// or in rows (aka. lines).
|
|
//
|
|
// I doubt whether columm mode is very useful, but I intend to support it.
|
|
//
|
|
// The original SPLIT plugin generates a normal table with rows and cells
|
|
// for line mode, but handles column mode with a single row. The separate
|
|
// rows in each column are defined with divs. This is imho Not good enough.
|
|
//
|
|
// Because I think it is an exotic case, I will handle column mode by
|
|
// flipping the matrices before generating the table. This is probably
|
|
// not very efficient, but keeps the code fairly readable.
|
|
//
|
|
if (isset($first) && $first == 'col') {
|
|
$cols = [] ;
|
|
$maxrows = count($rows) ;
|
|
|
|
for ($i = 0; $i < $maxcols; $i++) {
|
|
$cols[] = [] ;
|
|
}
|
|
foreach ($rows as $r) {
|
|
for ($i = 0; $i < $maxcols; $i++) {
|
|
if ($i < count($r)) {
|
|
$cols[$i][] = $r[$i] ;
|
|
} else {
|
|
$cols[$i][] = '' ;
|
|
}
|
|
}
|
|
}
|
|
|
|
$rows = $cols ;
|
|
$maxcols = $maxrows ;
|
|
}
|
|
|
|
// The bootstrap fluid grid can handle a maximum of 12 colums.
|
|
// Check this AFTER flipping the axis for column mode.
|
|
if ($maxcols > 12) {
|
|
return ( "<b>Fluid Grid can have a maximum of 12 columns</b><br/>") ;
|
|
}
|
|
|
|
// Handle the column widths.
|
|
//
|
|
// There are several cases:
|
|
// (not in the order in which they must be handled)
|
|
//
|
|
// (1) Colsize is not present
|
|
// - Share out the space as evenly as possible
|
|
//
|
|
// (2) Colsize is present
|
|
// It specifies the size of all columns
|
|
// The total size is <= 12
|
|
// - Assign exactly the specified sizes
|
|
//
|
|
// (3) Colsize is present
|
|
// It does not specifiy the size of all columns
|
|
// The total size plus the number of unsized columns is <= 12
|
|
// - Assign the specified sizes and share out the remaining
|
|
// units among the unsized columns
|
|
//
|
|
// The remaining cases are for some degree of compatibility with the
|
|
// SPLIT plugin.
|
|
//
|
|
// (4) Colsize is present
|
|
// It specifies the size of all columns in PIXELS
|
|
// The total size is > 12
|
|
// - Use the size as an approximate weighting
|
|
//
|
|
// (5) Colsize is present
|
|
// It specifies the size of some but not all columns in PIXELS
|
|
// The total size is > 12
|
|
// - Use the size as an approximate weighting, with a minimum size of 1.
|
|
//
|
|
// (6) Colsize is present
|
|
// All columns are specified in PERCENT.
|
|
// - Use the size as an approximate weighting, relative to 100, with a
|
|
// minimum size of 1.
|
|
// - The total can be less than 12, e.g. two columns with 25%|25%
|
|
// should translate to 3|3 and not 6|6
|
|
//
|
|
// (7) Colsize is present
|
|
// Some columns are specified in PERCENT, the rest are not specified.
|
|
// - Use the size as an approximate weighting, relative to 100, with a
|
|
// minimum size of 1.
|
|
// - Columns with an unspecified width should fill up remaining space,
|
|
// e.g. 3 columns with 25%|25% should translate to 3|3|6
|
|
//
|
|
// (8) Colsize is present
|
|
// Some columns are specified in PERCENT, some are specified in PIXELS.
|
|
// - Ingore the pixel values. Handle as above.
|
|
// (I don't have a good idea how to handle this case!)
|
|
//
|
|
|
|
// We will store the final widths in this array
|
|
$w_array = [] ;
|
|
|
|
if (isset($colsize)) {
|
|
// colsize is specified
|
|
|
|
// Check for a percent symbol on any column
|
|
$percent = ( strpos($colsize, '%') !== false ) ;
|
|
|
|
// Count the total size and the number of unsized columns
|
|
$tdsize = explode("|", $colsize);
|
|
$tdtotal = 0 ;
|
|
$tdnosize = 0 ;
|
|
$s_array = [] ;
|
|
|
|
// There are two parts to this algorithm:
|
|
// [1] Gathering information
|
|
// [2] Setting the final column sizes
|
|
|
|
// [1] Gathering information
|
|
// In this stage we read the colsize values and initialize
|
|
// $s_array = colsize values
|
|
// $tdtotal = total weighting
|
|
// $tdnosize = count of columns without a specified size
|
|
for ($i = 0; $i < $maxcols; $i++) {
|
|
if (isset($tdsize[$i]) && ( trim($tdsize[$i]) != '' )) {
|
|
$w = (int) trim($tdsize[$i]);
|
|
if ($w < 1) {
|
|
// treat 0 as unsized
|
|
$s_array[$i] = 0 ;
|
|
$tdnosize++ ;
|
|
} elseif ($percent && ( strpos($w, '%') === false )) {
|
|
// Percentage mode, but percent symbol not present.
|
|
$s_array[$i] = 0 ;
|
|
$tdnosize++ ;
|
|
} else {
|
|
// Normal case. Save the width and increment the total.
|
|
$s_array[$i] = $w ;
|
|
$tdtotal += $w ;
|
|
}
|
|
} else {
|
|
// Size not specified for this column.
|
|
$s_array[$i] = 0 ;
|
|
$tdnosize++ ;
|
|
}
|
|
}
|
|
|
|
if ($percent) {
|
|
// In percentage mode, the total wighting is always 100
|
|
$tdtotal = 100 ;
|
|
}
|
|
|
|
// [2] Setting the final column sizes
|
|
// In this stage we store the final column sizes in $w_array
|
|
if (( $tdtotal + $tdnosize ) <= 12) {
|
|
// Use the values as specified.
|
|
// Share the remaining space out among the unsized columns
|
|
$remaining = 12 - $tdtotal ;
|
|
|
|
for ($i = 0; $i < $maxcols; $i++) {
|
|
if ($s_array[$i] == 0) {
|
|
$w_array[$i] = ceil($remaining / $tdnosize) ;
|
|
$remaining -= $w_array[$i] ;
|
|
$tdnosize-- ;
|
|
} else {
|
|
$w_array[$i] = $s_array[$i] ;
|
|
}
|
|
}
|
|
} else {
|
|
// Use the values as approximate weightings
|
|
// Start by assigning every column a width of 1
|
|
for ($i = 0; $i < $maxcols; $i++) {
|
|
$w_array[$i] = 1 ;
|
|
}
|
|
|
|
// Now share out the rest
|
|
$i = 0 ;
|
|
$j = $maxcols ;
|
|
$h = 0 ;
|
|
$pcfill = true ;
|
|
|
|
while ($j < 12) {
|
|
// Increment the width if it is underweight
|
|
if (( $w_array[$i] / 12 ) < ( $s_array[$i] / $tdtotal )) {
|
|
$w_array[$i]++ ;
|
|
$j++ ;
|
|
}
|
|
|
|
// Increment column number and wraparound
|
|
$i++ ;
|
|
if ($i >= $maxcols) {
|
|
// Wraparound
|
|
$i = 0 ;
|
|
|
|
// $j must increase in each pass through the columns.
|
|
if ($h < $j) {
|
|
// Store the current position
|
|
$h = $j ;
|
|
} elseif ($pcfill) {
|
|
// In percentage mode, change 0% weighted columns to 100%
|
|
// so that they take up the remaining space.
|
|
$pcfill = false ;
|
|
for ($k = 0; $k < $maxcols; $k++) {
|
|
if ($s_array[$k] == 0) {
|
|
$s_array[$k] = 100 ;
|
|
}
|
|
}
|
|
} else {
|
|
// We get here in percentage mode, if the size is specified for
|
|
// all columns, but the total is less than 100%, e.g. two columns
|
|
// with 25%|25%, will result in 3|3.
|
|
break ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// colsize is not specified
|
|
// Share out the 12 units
|
|
$remaining = 12 ;
|
|
for ($i = 0; $i < $maxcols; $i++) {
|
|
// Share among the remaining columns.
|
|
// Round up as long as there is a remainder.
|
|
// Eventually it will be an integer.
|
|
$w_array[$i] = ceil($remaining / ($maxcols - $i)) ;
|
|
$remaining -= $w_array[$i] ;
|
|
}
|
|
}
|
|
|
|
//$result = "<div class='container-fluid" . ( !empty($customclass) ? " $customclass" : "") . "'>" ;
|
|
$result = "<div" . ( ! empty($customclass) ? " class='$customclass'" : "") . ">" ;
|
|
|
|
foreach ($rows as $r) {
|
|
// Start of the row
|
|
$result .= "<div class='row'>" ;
|
|
|
|
$j = 0 ;
|
|
while ($j < $maxcols) {
|
|
// Get the column width
|
|
$w = $w_array[$j] ;
|
|
|
|
// Get the content
|
|
$c = ( isset($r[$j]) ) ? $r[$j] : "" ;
|
|
|
|
if ($joincols) {
|
|
// Check for empty columns to the right
|
|
for ($k = $j + 1; $k < $maxcols; $k++) {
|
|
if (isset($r[$k]) && ( trim($r[$k]) != '' )) {
|
|
break ;
|
|
} else {
|
|
// Grab the space from the next column and skip it
|
|
$j = $k ;
|
|
$w += $w_array[$j] ;
|
|
}
|
|
}
|
|
}
|
|
|
|
// My current understanding is as follows.
|
|
// If you specify 'format' => 'html', then you must call
|
|
// TikiLib::lib('parser')->parse_data()
|
|
// to process the wiki syntax in the body text.
|
|
// If you specify 'format' => 'wiki', then the returned text will
|
|
// be parsed as wiki text, but this is potentially dangerous,
|
|
// because it is a mixture of html and wiki syntax.
|
|
$c = trim($c);
|
|
$c = wikiplugin_fluidgrid_rollback($c, $hashes);
|
|
$c = TikiLib::lib('parser')->parse_data($c);
|
|
$result .= "<div class='col-" . $devicesize . "-" . $w . "'>" . $c . "</div>" ;
|
|
|
|
// Increment the column number (because we are using while, not for)
|
|
$j++ ;
|
|
}
|
|
|
|
// End of the row
|
|
$result .= "</div>";
|
|
}
|
|
|
|
// Close HTML table (no \n at end!)
|
|
$result .= "</div>";
|
|
|
|
return $result;
|
|
}
|