'', //separator/delimter character. Leave empty so FoxCSV can determine separator from csv data without need to specify sep= 'case' => 0, //case-insensitive queries, set to 1 for case-sensitive queries 'regex' => 0, //simplified pagelist-like wildcards in queries. Set to 1 to use regular expression query patterns 'header' => 1,//no column/field headers included in data rows. Set to 1 to include a row of header names 'hideidx'=> 0, //default is to show index as column in auto templates 'idxname'=> 'Row', //default name for IDX 'textname' => 'Text', //default name for Text header field 'popups' => 0, //set to 1 to enable popup confirmation dialogues 'order' => 'natcase', //default sort order keyword 'class' => 'csvtable', //default class for auto tables 'sortable' => 1, //default class=sortable (sorting via js) for auto tables: Needs in config: $EnableSortable = 1; 'filterable' => 0, //if 1 class=filterable is added for auto tables: Needs ExtensionHub and Filterable extension installed 'editlinks' => 1, //by default show edit and delete buttons in auto template 'headeredit' => 1, //by default show header edit button in auto template 'editauth' => 'edit', //default auth level for edit and delete buttons to be shown 'new' => 'bottom', //by default new entry to bottom, set to 'top' to put new entry just below header row 'saveasnew' => 0, //set to 1 to show 'Save as New' button in edit form 'env' => 0, //set to 1 to enclose each item in double quotes 'copycsv' => 0, //set to 1 to show form for copying/importing csv/txt files 'fileedit' => 1, // set to 0 to inhibit file editing (no writing to files, only to wiki pages) 'decimals' => 2, // decimal places for 'num' fields number format 'decisep' => '.', // decimal separator for 'num' fields number format 'thousep' => ',', // thousands separator for 'num' fields number format 'addbuttonurl' => "$FarmPubDirUrl/fox/add-button.png", 'editbuttonurl' => "$FarmPubDirUrl/fox/edit-button.png", 'deletebuttonurl' => "$FarmPubDirUrl/fox/delete-button.png", )); // markup expression {(newidx [sep=..])} . Can be used as template var {$$(newidx .... )} in fox query form template $MarkupExpr['newidx'] = 'fxc_Next_Idx_Num_ME($pagename, $argp)'; function fxc_Next_Idx_Num_ME( $pagename, $argp ) { $src = $argp['source'] ?? $argp[''][0] ?? ''; if ($src=='') { $GLOBALS['FoxMsgFmt'][] = "%red%'''Error''' in form: '''newidx''' needs specific source. Check form, and correct wrong idxs%%"; return '000'; //error } $s = $argp['sep'] ?? $argp['seperator'] ?? $GLOBALS['FoxCSVConfig']['sep']; $rows = fxc_Get_Text_Rows( $pagename, $src ); if (strstr($rows[0], "IDX$s")) $n = fxc_Get_Row_Key($rows, "new", $s); else $n = count($rows); return $n; } //}}} // markup expression {(csv ......)} . Can be used as template var {$$(csv .... )} in fox query form template $MarkupExpr['csv'] = 'fxc_Display_ME($pagename, $argp)'; function fxc_Display_ME( $pagename, $argp ) { unset($argp['#']); $out = fxc_Display($pagename, 'mxcsv', $argp); return str_replace("\n\n","\n", $out); } //}}} // markup directive (:csv source= template= [sort= ] [query="..."] [seperator=".."] :) $MarkupDirectiveFunctions['csv'] = 'fxc_Display'; // main function for csv display function fxc_Display( $pagename, $directive, $args ) { global $FoxCSVConfig, $FoxMsgFmt, $RegexQueryExclude, $InputValues; // initialise option variables unset($args['#']); if ((isset($args['template']) OR isset($args['fmt'])) AND !isset($args['header'])) $args['header'] = 0; // by default no header row if we use template, not auto template $opt = array_merge($FoxCSVConfig, $args); #show($opt,'opt'); $source = $opt['source'] ?? $opt['src'] ?? array_shift($opt['']); if (empty($source)) return; $template = $opt['template'] ?? $opt['fmt'] ?? ''; //suppress display of edit and delete links/buttons, because they dont work when displaying file contents if (preg_match('/\.(csv|txt)$/i',$source,$m) && $FoxCSVConfig['fileedit']==0) $opt['editlinks'] = 0; // get text rows from page, page section or file $text = fxc_Get_Text ($pagename, $source); if ($text=='') return "'''Error:''' cannot get source text from '''$source'''"; $text = trim($text,"\r\n"); // get csv separator/delimiter direct from text, or via sep= parameter list($sep, $opt) = fxc_Set_Sep($text, $opt); // parse text into data 2-dimensional array, items per row $data = fxc_Parse_CSV($text, $sep); if (empty($data)) return "'''Error:''' Parsing csv data failed!"; #show ($data,'data'); // checking for errors in header names. Display errors by using markup (:foxmessages:) foreach ($data[0] as $k=>$v) { if (preg_match('/^[\d]|[^\-a-zA-Z0-9_]/', $v, $m)) $FoxMsgFmt[] = "'''Error:''' %black%invalid header name: %blue%'''$v''' %black%in $source%%"; } // display specific data item only if (isset($opt['cell'])) $cell = $opt['cell']; elseif (isset($opt[''][0]) && strpos($opt[''][0],'/')>0) $cell = array_shift($opt['']); if (isset($cell)) return fxc_Table_Item($data, $cell, $sep); // create data array with field names as keys for each item array_walk($data, function(&$a) use ($data) { $a = fxc_Array_Combine_Special($data[0], $a); }); // create header array, remove header row from data array $header = array_shift($data); // add SOURCE and IDX fields to data array, (:csv-delete ...:) and (:csv-edit ...:) rely on their presence foreach($data as $k=>$item) { $add1 = array('SOURCE'=>$source); if (array_key_exists('IDX',$item)) { $data[$k] = $add1 + $item; } else { //no IDX, add it! $add2 = array('IDX'=>$k+1); $data[$k] = $add2 + $add1 + $item; } } // filter data. Query-filter is constructed from query string and/or strings from header field names submitted $opt['query'] = $opt['query'] ?? $opt['filter'] ?? $opt['q'] ?? ""; fxc_Query($data, $header, $opt); // sort data if (isset($opt['sort'])) fxc_Multi_Column_Sort($data, $header, $opt['sort'], $opt['order']); // add CNT field, can be used with template var {$$CNT}, // giving descending result row count (Not the IDX of the item) foreach($data as $k=>$item) { $new = array('CNT'=>$k+1); $data[$k] = $new + $item; } // extract subset of data rows according to 'count=' parameter, using function on scripts/pagelist.php if (isset($opt['count'])) FPLTemplateSliceList($pagename, $data, $opt); $data = array_values($data); //re-index $rowcnt = count($data); $header = array('SOURCE'=>$source) + $header; //source field needed for possible var replacement, does not display as column #show($header,'header'); show($data,'data final'); //get permission to show edit and delete buttons if ($opt['editlinks'] ==1) $opt['editlinks'] = CondAuth($pagename, $opt['editauth']); // set a function call number, used to feed correct text to Iput Values for copycsv static $num = 0; $num++; // auto build simple table template for all fields, or fetch template string from a page $head = $body = $foot = $csvbody = ''; if ($template=='') { $auto = 1; $out = fxc_Auto_Table ($pagename, $data, $header, $opt, $num); } else { // use an external template. The header row may be supplied via markup above (:foxcsv ...:) markup, or not at all $auto = 0; $template = RetrieveAuthSection ($pagename, $template, $list=NULL, $auth='read'); if ($opt['editlinks']==0) //cut out csv-edit and csv-delete markup $template = preg_replace('/\\(:csv-(edit|delete)\\s*(.*?)\\s*:\\)/', '', $template); $tp = preg_split('/\(:template\s(\w+):\)/si', $template,-1,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE); if (count($tp)==1) $tpeach = $tp[0]; else { for($i=0; $i}] if (isset($tplast)) { $sums = array(); foreach($header as $k=>$v) { $sums['SUM_'.$k] = array_sum(array_column($data,$v)); } $str = FoxVarReplace($pagename, $sums, '', $tplast); $foot .= preg_replace('/\{\$\$\(?(.*?)\)?\}/'," ",$str); } $out = $head.$body.$foot; } // return output as wiki markup, Markup to HTML will be done later by PmWiki return $out; } //}}} // determine and set separator/delimiter function fxc_Set_Sep( $text, $opt ) { $sep = $opt['sep'] ?? $opt['csvsep'] ?? $GLOBALS['FoxCSVConfig']['sep']; if ($sep=='') if (preg_match('/^(")?IDX\1?(;)/', $text, $m)) { $sep = $opt['sep'] = $m[2]; if ($m[1]=='"') $opt['quotes'] = 1; //assuming data elements are enclosed in dbl-quotes } elseif (preg_match('/^(")?[a-zA-Z][-\w]*\1?(.)/', $text, $m)) { //guessing sep is first non-alpha-numeric char after alphanum start $sep = $opt['sep'] = $m[2]; if ($m[1]=='"') $opt['quotes'] = 1; //assuming data elements are enclosed in dbl-quotes } else $sep = ' '; $opt['sep'] = $sep; if ($sep=='\t') $sep = "\t"; //catching tab character return array($sep, $opt); } //}}} // get text from file or page (section) function fxc_Get_Text( $pagename, $src ) { // get rows array from a file in uploads if (preg_match('/\.(csv|txt)$/i',$src,$ext)) { $txt = @fxc_Get_File_Content($pagename, $src); if ($txt===FALSE) $GLOBALS['FoxMsgFmt'][] = "'''Error:''' could not find file '''$src'''"; } else { $anc = strstr($src,'#'); if ($src[0]=='#') $src = $pagename.$src; $pn = MakePageName($pagename, $src); $src = $pn.$anc; $txt = RetrieveAuthSection($pn, $src); } if (empty($txt)) return ''; if (array_key_exists(1,$ext) && strtolower($ext[1])=="txt") { $txt = "Text\n".$txt; // setting header field 'Text' for text import } return $txt; } //}}} // get file contents from uploaded (attached) file function fxc_Get_File_Content( $pagename, $filename ) { global $UploadDir, $UploadPrefixFmt; $fpn = explode('/',$filename); if (isset($fpn[1])) { $fname = $fpn[1]; $fpre = "/".$fpn[0]; } else { $fname = $fpn[0]; $fpre = $UploadPrefixFmt; } $uppath = FmtPageName("$UploadDir$fpre", $pagename); $csvfile = FmtPageName("$uppath/$fname", $pagename); return file_get_contents($csvfile); } //}}} // parse text and create two-dimensional data array function fxc_Parse_CSV( $text, $sep ) { $data = explode("\n", $text); $data = fxc_Remove_Empty_Rows( $data, $sep); #show($data,'rows'); // text import with single 'Text' header field if ($data[0]=='Text') { foreach ($data as &$row) $row = array($row); return $data; } $pat = array( '%25','%2c', '%0a', '%0d', '\\', '%22', '`"'); //tokens $rep = array( '%', $sep, "\n", "\r", "\n", '"', '"'); //restored to characters foreach ($data as $i => $row) { # show($row,'row before'); $row = str_getcsv($row, $sep, "\"", "`"); //parse the items in rows [no enclosure escape character, use double ""] # show($row,'row parsed'); foreach ($row as &$it) { $it = str_replace($pat, $rep, $it); // restore characters from tokens } # show($row,'row adjusted'); $data[$i] = $row; } $data[0] = fxc_Normalise_Header( $data[0] ); #show($data,'data'); return $data; } //}}} // making header names with pattern (adjusting input to get valid names) function fxc_Normalise_Header ( $header ) { $hdpat = array( "/'/" => '', # strip single-quotes "/^\d+/" => 'N$0', #prepend leading digits with 'N' "/[^_[:alnum:]]+/" => ' ', # convert everything else to space '/ /' => '-', # convert space to hyphen ); $hdr = array_map('trim', $header); //assuming first row is header $header = PPRA($hdpat, $hdr); //sanitising header names //check for duplicates in header $duplis = array_diff_assoc($header, array_unique($header)); if (!empty($duplis)) { $GLOBALS['FoxMsgFmt'][] = "%red%'''Error:''' header has duplicate field names. Edit header!"; foreach ($duplis as $k=>$v) $header[$k] = $v."-duplic"; } foreach ($header as $name) if (count($header)==1 ) $GLOBALS['FoxMsgFmt'][] = "%green%'''Warning:''' just one header field! Check if 'sep=..' is correct. "; return $header; } //}}} // Get item by header and idx: name/idx or col/idx {(csv /)} function fxc_Table_Item( $data, $ref, $sep ) { $ref = explode('/', $ref); if (!isset($ref[1])) return "'''Error:''' wrong or no name or idx!"; $col = $ref[0]; $row = $ref[1]; //get row by idx number if ($data[0][0]=='IDX') { foreach ($data as $k => $rw) if ($rw[0]==$row) $row = $k; } if (!array_key_exists($row,$data)) return "'''Error:''' row '''$row''' does not exist!"; $el = $data[$row]; if (!preg_match('/^\d+$/',$col)) $el = array_combine($data[0], $el); if (!array_key_exists($col,$el)) return "'''Error:''' header column '''$col''' does not exist, or wrong separator!"; return $el[$col]; } //}}} // filter data array according to query (regular expression or PmWiki pagelist query syntax) function fxc_Query( &$data, $header, $opt ) { $query = ParseArgs($opt['query']); $quin = (isset($query[''])) ? implode(',',$query['']) : ''; $quex = (isset($query['-'])) ? implode(',',$query['-']) : ''; unset($query['#'], $query[''], $query['-']); foreach($header as $k=>$v) { if (array_key_exists($k,$opt) && !empty($opt[$k])) { if (array_key_exists($k,$query)) $query[$k] = ",".$query[$k].",".$opt[$k]; //combine strings if we got double keys else $query[$k] = ",".$opt[$k]; $query[$k] = trim($query[$k], ","); continue; } } foreach($header as $k=>$v) { if (!isset($query[$k])) $query[$k] = ''; if ($quin!='') $query[$k] .= ",".$quin; if ($quex!='') $query[$k] .= ",-".$quex; } if(!array_filter($query)) return; // create regex patterns for including and excluding $exclude = $include = array(); foreach($query as $field => $pat) { if(!in_array($field, $header)) continue; if ($opt['regex']==0) { list($incl, $excl) = GlobToPCRE($pat); } $include[$field] = ($opt['regex']==0)? $incl : $pat; $exclude[$field] = ($opt['regex']==0)? $excl : ""; } // check each data item in turn if (!empty($query)) $matches = array(); foreach($data as $i=>$item) { foreach($include as $k => $pat) { if ($pat=="") continue; if (isset($opt['case']) && $opt['case']==0) $pat = "(?i)".$pat; if (preg_match("($pat)", $item[$k])) { $matches[$i] = $data[$i]; continue 2; } } } if ($opt['regex']==0) //for non-regex queries we may have exclude patterns foreach($matches as $i=>$item) { foreach($exclude as $k => $pat) { if ($pat=="") continue; if (isset($opt['case']) && $opt['case']==0) { $pat = "(?i)".$pat; } if (preg_match("($pat)",$item[$k])) { unset($matches[$i]); continue 2; } } } $data = $matches; } //}}} // sort a multi-column data array by up to four header fields function fxc_Multi_Column_Sort(&$data, $header, $sort, $order ) { global $FoxCSVConfig; global $FoxMsgFmt; $sort = explode(',',$sort); if (empty($sort[0])) return; $cnt = count($sort); //we want max 4 sort names if ($cnt > 4) { $FoxMsgFmt[] = "Warning: no more than 4 sort names allowed!"; for ($i=$cnt-1; $i>3 ; $i--) unset($sort[$i]); //reduce list to 3 names $cnt = 4; } $order = ParseArgs($order); unset($order['#']); $dir = $col = $flags = array(); // set sort direction, general or by sort field foreach($sort as $k=>$n) { if(substr($n, 0,1)=='-') { $sort[$k] = ltrim($n, '-'); $dir[$k] = SORT_DESC; } else { $sort[$k] = $n; $dir[$k] = SORT_ASC; } if (isset($order['-'])) $dir[$k] = SORT_DESC; if (isset($order[$n])) { if (substr($order[$n], 0,1)=='-') { $order[$n] = ltrim($order[$n], '-'); $dir[$k] = SORT_DESC; //the specific overrides the general } else $dir[$k] = SORT_ASC; } } // get columns for array_multisort(), set flags, pass to array_multisort() according to number of sort columns $so = array(); foreach($sort as $k=>$n) { if (!in_array($n,$header)) { $FoxMsgFmt[] = "Error: Could not sort Data. Invalid ''sort'' parameter!"; return; } $col[$k] = array_column($data, $n); // set sort order flags for each sort field if (in_array($n,array_keys($order))) $so[$k] = $order[$n]; else if (isset($order[''])) $so[$k] = $order[''][0]; else if (isset($order['-'])) $so[$k] = $order['-'][0]; else $so[$k] = $FoxCSVConfig['order']; //default // setting sort flags array $fl for array_multisort() switch($so[$k]) { case 'reg': case 'regular': $fl[$k] = SORT_REGULAR; break;// default; compare items normally (don't change types) case 'nat': case 'natural': $fl[$k] = SORT_NATURAL; break; // compare items as strings using "natural ordering" like natsort() case 'natcase': $fl[$k] = SORT_NATURAL|SORT_FLAG_CASE; break; // sort as strings natural and case-insensitively case 'str': case 'string': $fl[$k] = SORT_STRING; break; // compare items as strings case 'strcase': $fl[$k] = SORT_STRING|SORT_FLAG_CASE; break;// sort as strings and case-insensitively case 'num': case 'numeric': $fl[$k] = SORT_NUMERIC; break; // compare items numerically case 'loc': case 'locale': $fl[$k] = SORT_LOCALE_STRING; break; // compare items as strings, based on the current locale. It uses the locale, which can be changed using setlocale() default: $fl[$k] = SORT_REGULAR; break;// default; compare items normally (don't change types) } } switch ($cnt) { //do the ordering according to sort col number case '0': $FoxMsgFmt[] = "Error: Missing or invalid sort parameters!"; break; case '1': array_multisort($col[0], $dir[0] , $fl[0], $data); break; case '2': array_multisort($col[0], $dir[0] , $fl[0], $col[1], $dir[1], $fl[1], $data); break; case '3': array_multisort($col[0], $dir[0] , $fl[0], $col[1], $dir[1], $fl[1], $col[2], $dir[2], $fl[2], $data); break; case '4': array_multisort($col[0], $dir[0] , $fl[0], $col[1], $dir[1], $fl[1], $col[2], $dir[2], $fl[2], $col[3], $dir[3], $fl[3],$data); break; } } //}}} function fxc_Auto_Table( $pagename, $data, $header, $opt, $num ) { // show specific columns only, or to exclude certain columns $source = $header['SOURCE']; if (isset($opt['show'])) $header = fxc_Fields($opt['show'], $header); //remove fields not for display unset($header['SOURCE']); if ($opt['hideidx']==1) unset($header['IDX']); // set cell alignment $ralf = $calf = array(); if (isset($opt['ralign'])) $ralf = fxc_Fields($opt['ralign'], $header); //right aligned columns if (isset($opt['calign'])) $calf = fxc_Fields($opt['calign'], $header); //centred columns // build template string $tmpl= "(:table:)\n"; $tmpl = ''; $header = array_values($header); //index #show($header); // format numbers in 'num=' specified fields: displays rounded to n decimal places, sets decimal and thousands separtors if (isset($opt['num'])) { $numf = fxc_Fields($opt['num'], $header); $ralf = array_merge($ralf, $numf); // num=... number fields get right-aligned foreach ($data as &$row) foreach ($row as $k => $it) if (in_array($k, $numf) && is_numeric($it)) $row[$k] = number_format($it, $opt['decimals'], $opt['decisep'], $opt['thousep']); } foreach ($header as $k=>$v) { $nr = ($k==0) ? "nr" : ''; if (in_array($v, $calf)) $al='center'; elseif (in_array($v, $ralf)) $al='right'; else $al='left'; $tmpl .= "(:cell$nr align=$al:){\$\$".$v."} \n"; } $lblnew = (isset($opt['labelnew'])) ? "label='".$opt['labelnew']."'" : ''; $lbledit = (isset($opt['labeledit'])) ? "label='".$opt['labeledit']."'" : ''; $lbldelete = (isset($opt['labeldelete'])) ? "label='".$opt['labeldelete']."'" : ''; // insert delete and edit buttons for each row if ($opt['editlinks']==1) { if ($GLOBALS['FoxCSVConfig']['saveasnew']==1 && $opt['saveasnew']==1) { if ($opt['new']=='top') $opt['saveasnew'] = 'top'; else $opt['saveasnew'] = 'bottom'; } $template = $tmpl."(:cell:)(:csv-delete target={\$\$SOURCE} idx={\$\$IDX} sep=\"".$opt['sep']."\" $lbldelete:)". "(:csv-edit {\$\$SOURCE} idx={\$\$IDX} sep={$opt['sep']} $lbledit ". (isset($opt['env']) ? " env={$opt['env']} " : ''). (isset($opt['multiline']) ? " multiline={$opt['multiline']} " : ''). (isset($opt['saveasnew']) ? " saveasnew={$opt['saveasnew']} " : ''). ":)\n"; } else $template = $tmpl."\n"; $head = $foot = $body = ''; // make table rows (body) for ($k=0; $k$hd) { $nr = ($k==0) ? "nr" : ''; if ($hd=='IDX') $foot .= "(:cell$nr:) \n"; else if (in_array($hd, $sumf)) { $col = array_column($data, $hd); $sum = 0; foreach ($col as $i => $c) { if (isset($numf) && in_array($hd, $numf)) { $c = str_replace($opt['thousep'],'', $c); if ($opt['decisep']!='.') $c = str_replace($opt['decisep'],'.', $c); } if (is_numeric($c)) $sum += (float)$c; } if (isset($numf) && in_array($hd, $numf)) $sum = number_format($sum, $opt['decimals'], $opt['decisep'], $opt['thousep']); $sum = "%tsum%".$sum; if (in_array($hd, $calf)) $al='center'; elseif (in_array($hd, $ralf)) $al='right'; else $al='left'; $foot .= "(:cell$nr align=$al:)$sum \n"; } else $foot .= "(:cell$nr:) \n"; //no sum for this column } } //make csv table; insert copy form first in head if ($opt['editlinks']==1) { $copyform = ''; if ($opt['copycsv']==1) { fxc_Copy_To_CSV ($pagename, $data, $header, $opt, $num); // generate new csv text and put into $InputValues $copyform = "\n(:fox copycsv class='csvform':)". "(:input submit post '$[Copy to Page]':) (:input text target size=9:)". "(:foxtemplate \"{\$\$text$num}\":)\n(:input hidden text$num :)". "(:foxend copycsv:)\n"; } // add toolbar with csv-action forms if (!empty($opt['toolbar']) ) $head .= "(:div class='csvtoolbar':)(:csv-index $source:)(:csv-reindex $source:)(:csv-trim $source:)(:csv-quote $source:)". "(:csv-reformat $source:)(:csv-newcol $source:)(:csv-coldel $source:)(:csv-export $source:)$copyform\n(:divend:)\n"; elseif ( $opt['copycsv']==1 ) $head .= $copyform; } // build header row as first row for output, the others are added after var substitutions by FoxVarReplace if ($opt['header']==0) $head .= "(:table:)\n"; else { $htpl = ''; $htpl .= "(:table class='$class':)\n"; foreach ($header as $k=>$v) { $nr = ($k==0) ? "nr" : ''; if ($v=='IDX') $v = $opt['idxname']; // give IDX a friendlier name, but IDX stays internally as special name if ($v=='Text') $v = $opt['textname']; if (in_array($v, $calf)) $al='center'; elseif (in_array($v, $ralf)) $al='right'; else $al='left'; $htpl .= "(:head$nr align=$al:)$v \n"; } $header['SOURCE'] = $source; // add SOURCE back in if ($opt['editlinks']==1) { $nw = ($opt['new']=='top') ? 'newtop' : 'new'; $htpl .= "(:head:)". (($opt['headeredit']==1)? "(:csv-edit {\$\$SOURCE} idx=header sep=\"".$opt['sep']."\" $lbledit:)" : ""). "(:csv-edit {\$\$SOURCE} idx='$nw' sep=\"".$opt['sep']."\" $lblnew ". (isset($opt['multiline']) ? " multiline={$opt['multiline']} " : ''). ":) \n"; } $head .= FoxVarReplace($pagename, $header, '', $htpl)."\n"; } $foot .= "(:tableend:)"; return $head.$body.$foot; } //}}} // create csv text and set to $InputValues for copy form function fxc_Copy_To_CSV( $pagename, $data, $fields, $opt, $num ) { $sep = $opt['newsep'] ?? $opt['sep']; $head = ($GLOBALS['EnablePostDirectives']==1)? "(:csv #data sep=$sep:)\n(:if false:)\n[[#data]]\n" : ""; $tmpl = ''; foreach ($fields as $n) { $tmpl .= "{\$\$".$n."}".$sep; $head .= $n.$sep; } $tmpl = rtrim($tmpl,$sep)."\n"; //trim trailing separators, add line breaks $head = rtrim($head,$sep)."\n"; $body = ''; if (array_key_exists('header',$data)) unset($data['header']); // enclose items in quotes if they contain separator or newline characters foreach ($data as &$row) foreach ($row as &$it) if (preg_match('/\n/',$it) OR strstr($it, $sep)) $it = '"'.$it.'"'; // "enclose item" if needed #show($data,'data makecsv'); foreach ($data as $k=>$v) { $str = FoxVarReplace($pagename, $v, '', $tmpl); $str = fxc_Make_CSV_Row ($str, $sep, $opt['env'])."\n"; $body .= preg_replace('/\{\$\$\(?(.*?)\)?\}/'," ",$str); } $foot = ($GLOBALS['EnablePostDirectives']==1)? "[[#dataend]](:ifend:)\n\n" : ""; // set $InputValues for hidden input field when copoying/importing $text = $head.$body.$foot; #show($text,'copytext:'); $GLOBALS['InputValues']['text'.$num] = $text; } //}}} function fxc_Write_To_File ( $pagename, $filename, $text ) { global $UploadDir, $UploadPrefixFmt, $FoxMsgFmt; $fpn = explode('/',$filename); if (isset($fpn[1])) { $fname = $fpn[1]; $fpre = "/".$fpn[0]; } else { $fname = $fpn[0]; $fpre = $UploadPrefixFmt; } $uppath = FmtPageName("$UploadDir$fpre", $pagename); $file = FmtPageName("$uppath/$fname", $pagename); $backup = preg_replace("/.csv$/","-backup.csv",$file); if (file_exists($file)) copy($file, $backup); $fp = fopen($file, 'w'); fwrite($fp, $text); fclose($fp); } //}}} // make single text line into a valid csv record (row) function fxc_Make_CSV_Row( $row, $sep, $env='' ) { #show($row,'row'); $items = str_getcsv($row, $sep, "\"", "`"); foreach($items as &$str) { $str = fxc_Make_CSV_Item($str, $sep, $env); } $row = implode($sep, $items); //return row as string return $row; } //}}} // make single csv item. Replace line breaks with tokens, add quotes to quoted parts, enclose items in quotes if needed function fxc_Make_CSV_Item( $str, $sep, $env='' ) { #show($str,'str 1'); $str = preg_replace('/\r\n?/', "\n", $str); // replace any CR or CRNR with NL $str = preg_replace('/(?$rows); foreach($rows as $k=>&$v) { $rows[$k] = trim($v,"\r\n"); //not trimming seps at right! if (empty($rows[$k])) unset($rows[$k]); } $rows = array_values($rows); //reindex return $rows; } //}}} // returns a field array as subset of header according to arg parameter [called from fxc_Auto_Tables] function fxc_Fields( $arg, $header ) { if ($arg==1) return $header; $args = explode(',', $arg); $flg = 1; foreach ($args as $k=>$v) if ($v[0]=='-') { $args[$k] = substr($v,1); $flg = 0; continue; } if ($flg==1) return $args; else return array_diff($header, $args); } //}}} // combines arrays of different sizes by special paddings. Code from comment on PHP Manual:array_combine function fxc_Array_Combine_Special( $a, $b, $pad = TRUE ) { $acount = count($a); $bcount = count($b); // more elements in $a than $b but we don't want to pad either if (!$pad) { $size = ($acount > $bcount) ? $bcount : $acount; $a = array_slice($a, 0, $size); $b = array_slice($b, 0, $size); } else { // more headers than row fields if ($acount > $bcount) { $more = $acount - $bcount; // Add empty strings to ensure arrays $a and $b have same number of elements $more = $acount - $bcount; for($i = 0; $i < $more; $i++) { $b[] = " "; //HB: add space, not empty string, so replacement variable can match something! } // more fields than headers } else if ($acount < $bcount) { $more = $bcount - $acount; // fewer elements in the first array, add extra keys for($i = 0; $i < $more; $i++) { $key = 'extra_0' . $i; $a[] = $key; } } } return array_combine($a, $b); } //}}} function fxc_Get_Row_Key( $rows, $idx, $sep ) { $idnr = array(); $cnt = count($rows); for($k=1; $k < $cnt; $k++) { $r = explode($sep,$rows[$k]); if (is_numeric($r[0])) { if ($r[0]==$idx) return $k; //return row key $idnr[] = $r[0]; } } $max = (!empty($idnr)) ? (int)max($idnr) : 0; if (preg_match('/^new(top|\d+)?/', $idx)) return $max + 1; //return next from highest for new entries return $idx; //return idx if no row key is found } //}}} //===================================// // csv mod button markup {[foxcsv... target={$$SOURCE} idx={$$IDX} label=' X ']} Markup('foxcsvact','directives','/\(:csv-(del|delete|index|reindex|reformat|trim|quote|table|coldel|newcol|import|export)\\s+(.*?)\\s*:\)/', "fxc_Action_Form_Fmt"); # Creates the HTML output for csv action button function fxc_Action_Form_Fmt( $m ) { global $FoxCSVConfig, $ScriptUrl, $EnablePathInfo; extract($GLOBALS['MarkupToHTML']); $act = $m[1]; $opt = ParseArgs($m[2]); unset($opt['#']); $opt[''] = (array)@$opt['']; if ($act=='import') { $filename = array_shift($opt['']); } $target = $opt['target'] ?? array_shift($opt['']) ?? $pagename; #show($opt,'opt'); if ($target=='') return; if ((empty($opt['idx']) || $opt['idx']=='IDX') && ($act=='del' || $act=='delete')) return; $idx = $opt['idx'] ?? ''; $csum = '$[Table updated]'; $imageurl = ''; // set defaults for labels and titles (mouse-over tooltips) if (isset($opt['label'])) { $label = $opt['label']; $title = $opt['title'] ?? $label; } // set labels, titles, messages else switch ($act) { case 'del': case 'delete': $imageurl = $FoxCSVConfig['deletebuttonurl']; $label = 'X'; $title = "$[Delete row] $idx"; $onclickmessage = '$[Please confirm: Do you want to delete this csv row?]'; $csum = '$[Table row deleted]'; break; case 'index': $label = '$[Index]'; $title = "$[Index] $target"; $onclickmessage = '$[Please confirm: Do you want to index this csv table?]'; $csum = '$[CSV Table Index added]'; break; case 'reindex': $label = '$[Re-Index]'; $title = "$[Re-index] $target"; $onclickmessage = '$[Please confirm: Do you want to reindex this csv table?]'; $csum = '$[CSV Table re-indexed]'; break; case 'reformat': $label = '$[Reformat]'; $title = "$[Reformat with new separator] $target"; $onclickmessage = '$[Please confirm: Do you want to reformat this csv table?]'; $csum = '$[CSV Table reformatted]'; break; case 'trim': $label = '$[Trim Quotes]'; $title = "$[Trim surrounding quotes and white spaces from] $target"; $onclickmessage = '$[Please confirm: Do you want to trim items of this csv table?]'; $csum = '$[CSV Items trimmed]'; break; case 'quote': $label = '$[Add Quotes]'; $title = "$[Enclose with double quote marks on] $target"; $onclickmessage = '$[Please confirm: Do you want to add quote marks to items of this csv table?]'; $csum = '$[CSV Items enclosed in quote marks]'; break; case 'table': $label = '$[Make Table]'; $title = "$[Make Table] $target"; $onclickmessage = '$[Please confirm: Do you want to convert csv table into simple table format?]'; break; case 'coldel': $label = '$[Delete Column]'; $title = "$[Delete Column on] $target"; $onclickmessage = '$[Please confirm: Do you want to delete this column from the table?]'; $csum = '$[Column deleted]'; break; case 'newcol': $label = '$[Add New Column]'; $title = "$[Add New empty Column to] $target"; $onclickmessage = '$[Please confirm: Do you want to add this new column to the table?]'; $csum = '$[New column added]'; break; case 'import': $label = '$[Import]'; $title = "$[Import] $target"; case 'export': $label = '$[Export]'; $title = "$[Export] $target"; } $sep = $opt['sep'] ?? $FoxCSVConfig['sep'] ?? ''; $env = $opt['env'] ?? $FoxCSVConfig['env'] ?? 0; $col = $opt['col'] ?? ''; // sanitising target parameter, so it has group, name and possible anchor $csvfile = 0; if (preg_match('/\.(csv|txt)$/i', $target, $m)) { //file update $csvfile = 1; //file edit flag $TargetPageUrl = PUE(($EnablePathInfo) ? "$ScriptUrl/$pagename" : "$ScriptUrl?n=$pagename"); $tpn = $pagename; } else { //page /section update $tt = explode("#",$target); if (empty($tt[0])) $tpn = $pagename; else $tpn = MakePageName($pagename, $tt[0]); $target = (isset($tt[1])) ? $tpn."#".$tt[1] : $tpn; $TargetPageUrl = PUE(($EnablePathInfo) ? "$ScriptUrl/$target" : "$ScriptUrl?n=$tpn"); } // javascript delete message dialogue $onclick = ($FoxCSVConfig['popups']==true)? "onclick='return confirm(\"{$onclickmessage}\")'" : ""; // construct HTML delete button as output, additional input given via foxfilter function $out = "\n
". "". "". "". "". "". "". "". "". "". "". (isset($opt['newsep'])? "" : ""). ($env==1 ? "" : ""). (isset($opt['new']) && $opt['new']=='top' ? "" : ""). ($act=='reformat' ? "NewSep:" : ""). ($act=='coldel' ? "Col:" : ""). ($act=='newcol' ? "New:" : ""). ($act=='newcol' ? " After:" : ""). "". "". ($act!='import' ? "" : ""). (!empty($imageurl) ? "" : ""). ($act=='import' ? " to:" : ""). ($act=='export' ? " to:" : ""). "
"; return Keep(FmtPagename($out,$tpn)); } //}}} // gets text content from csv file and fills template $FoxFilterFunctions['csvimport'] = 'fxc_Import_Filter'; function fxc_Import_Filter ( $pagename, $fx) { if (!preg_match('/\.(csv|txt)$/i',$fx['filename'],$m)) FoxAbort($pagename, "$[Error: Wrong file type!]"); $text = fxc_Get_File_Content($pagename, $fx['filename']); if (strtolower($m[1])=='csv') $tmpl = "(:csv #data:)\n(:if false:)\n[[#data]]\n".$text."\n[[#dataend]]"; //text into data section and added csv markup else $tmpl = $text; //plain text import $fx['foxtemplate'] = $tmpl; $fx['csum'] = 'CSV file import'; return $fx; } //}}} // pre-process input // if submit post2 button is used, 'saveasnew' option is processed for correct placement // with calls to fxc_Make_CSV_Item() quoted text gets double quoted, newline replaced with tokem, items with separator characters enclosed with quotes #$FoxFilterFunctions['csvedit'] = 'fxc_Edit_Filter'; function fxc_Preprocess_Input($pagename, &$fx) { #DEBUG show($fx,'fx Edit Filter begin'); if ($fx['csvact']=='export') { $GLOBALS['EnableFoxDefaultMsg'] = 0; return; } unset($fx['put']); //submit post2 'Save as New': change csvact to 'addnew' if (isset($fx['post2'])) $fx['csvact'] = 'addnew'; if ($fx['csvact']!='replace' OR $fx['csvact']!='addnew') return; // get header field names and sep from template (reverse engineer) // correct any field input from these names $temp = $fx['foxtemplate']; #show($temp,'template'); $env = (!empty($fx['csvenv'])) ? 1 : 0; if (preg_match_all('~\{\$\$(.*?)\}(.)?~', $temp, $m)) { $sep = $m[2][0]; foreach($m[1] as $k=>$name) { if (!empty($fx[$name])) { $fx[$name] = fxc_Make_CSV_Item($fx[$name], $sep, $env); } } } #DEBUG show($fx,'fx Edit Filter end'); #exit; return; } //}}} // processing various csv-modifying actions via parameter 'csvact=...' from a fox form (i.e. from fxc_Action_Form_Fmt) // called via 'foxaction=csv' from FoxProcessTargets() in fox.php function FoxCSV_Update( $pagename, $text, $newline, $fx ) { global $FoxDebug, $FoxCSVConfig, $FoxMsgFmt; if($FoxDebug) { echo "
FoxCSV_Update> "; show($fx['csvidx'],'idx'); show($newline,'newline'); } if (empty($fx['csvact'])) { $FoxMsgFmt[] = "Error: no csv update action provided"; return $text; } $idx = $fx['csvidx']; $env = $fx['csvenv'] ?? ''; if (isset($fx['csvfile']) && $fx['csvfile']==1) $text = fxc_Get_Text($pagename, $fx['target']); $text = trim($text,"\n\r"); list($sep, $fx) = fxc_Set_Sep($text, $fx); if ($sep=='\t') $sep = "\t"; $rows = explode("\n", $text); $rows = fxc_Remove_Empty_Rows($rows, $sep); $cnt = count($rows); if($FoxDebug >1) show($rows,'rows old'); #show($idx,'idx'); if (empty($rows)) return $text; // make idx for new item for custom form submissions if ($fx['csvact']=='addnew') { if ($idx=='new') $idx = $cnt; //add to bottom if ($idx=='newtop') $idx = 0; //add to top } // 'Save as New' post (submit post2 button) if (isset($fx['post2'])) { if (!isset($fx['saveasnew'])) $fx['saveasnew'] = $FoxCSVConfig['saveasnew']; if ($fx['saveasnew']==1) $fx['saveasnew'] = 'bottom'; if (isset($fx['IDX'])) $fx['IDX'] = $cnt; //put new IDX switch ($fx['saveasnew']) { //saveasnew==1 is changed to 'bottom' in AutoTable function! case 'bottom': $idx = $cnt; break; case 'top': $idx = 0; break; case 'above': if ($fx['csvidx']>0) $idx = $fx['csvidx'] - 1; break; case 'below': break; case '0': FoxAbort($fx['foxpage'], "Error: 'Save as New' is not enabled!"); break; } } switch ($fx['csvact']) { // add a new record after row $idx case 'addnew': $pos = (int)$idx +1; array_splice($rows, $pos, 0, $newline); break; // update single row (replace original row) case 'replace': if ($idx=='0') { // normalise header fields! $row = fxc_Normalise_Header(str_getcsv($newline, $sep)); foreach($row as &$it) $it = fxc_Make_CSV_Item($it, $sep, $fx); $rows[0] = implode($sep, $row); } else $rows[$idx] = $newline; break; // remove single row according to idx given. row number can be different from idx number for indexed tables case 'del': case 'delete': $key = fxc_Get_Row_Key($rows, $idx, $sep); unset($rows[$key]); $FoxMsgFmt[] = "CSV row deleted"; break; // add a first field with index numbers and 'IDX' in the header row case 'index': if (preg_match('/^IDX/', $rows[0])) FoxAbort($fx['redir'], "Error: IDX field already present, table may need re-indexing instead. No index added."); $rows[0] = "IDX".$sep.$rows[0]; for ( $i=1; $i < $cnt; $i++ ) { $n = (isset($fx['reverseindex'])) ? $cnt-$i : $i; $rows[$i] = $n.$sep.$rows[$i]; } $FoxMsgFmt[] = "CSV table index added"; break; // refresh index numbers if csv table has index (IDX column as first column) case 'reindex': if (preg_match("/^(?!IDX$sep).*/", $rows[0])) { FoxAbort($fx['redir'], "Error: IDX field missing, table may need indexing first. Reindex failed"); return $text; } for ( $i=1; $i<$cnt; $i++ ) { $n = (isset($fx['reverseindex'])) ? $cnt-$i : $i; if (preg_match("/^([\d]+$sep)/", $rows[$i], $m)) $rows[$i] = preg_replace('/^([\d]+)/', $n, $rows[$i]); else { FoxAbort($fx['redir'], "Error: check row $k for invalid IDX entry! Reindex failed"); return $text; } } $FoxMsgFmt[] = "CSV table reindexed"; break; // replace separator with new separator case 'reformat': if (empty($fx['newsep']) OR strlen($fx['newsep'])>1) { FoxAbort($fx['redir'], "Error: new separator 'newsep' missing or bad. Reformat failed."); return $text; } if ($fx['newsep']=='\t') $fx['newsep'] = "\t"; foreach($rows as &$row) { $row = str_getcsv($row, $sep, "\"", "`"); foreach($row as &$it) $it = fxc_Make_CSV_Item($it, $sep, $env); $row = implode($fx['newsep'], $row); } $FoxMsgFmt[] = "CSV table reformatted"; break; // rewrite csv data table with PmWiki simple table markup case 'table': $rows[0] = "||class='sortable csvtable'\n||!".preg_replace("~(\s?$sep\s*)~", " ||!", $rows[0])." ||"; $rows[0] = str_replace("IDX", $FoxCSVConfig['idxname'], $rows[0]); for($k=1; $k (int)$hdcnt) $num = $hdcnt; //column number should not be higher than count, i.e. just one higher than actual //for each row: make csv array, slice into two, add the new between and rebuild row foreach($rows as $k=>$v) { $row = str_getcsv($v, $sep); foreach($row as &$it) $it = fxc_Make_CSV_Item($it, $sep); $rwa = array_slice($row,0,(int)$num); $rwb = array_slice($row,(int)$num); $new = ($k==0) ? $name : ""; $a = (isset($rwa[0])) ? trim(implode($sep,$rwa)).$sep : ''; $b = (isset($rwb[0])) ? $sep.trim(implode($sep,$rwb)) : ''; $rows[$k] = $a.$new.$b; } $FoxMsgFmt[] = "CSV table column added"; break; // save all to csv file case 'export': $fx['target'] = $fx['exportto']; $FoxMsgFmt[] = "Success: exported text to file {$fx['exportto']}"; break; } if ($FoxDebug >1) show($rows,'rows new'); #exit; //rebuild text from rows array $text = implode("\n", $rows); $text = rtrim($text); if (isset($fx['csvfile']) && $fx['csvfile']==1) { if ($GLOBALS['FoxCSVConfig']['fileedit']==0) FoxAbort($fx['redir'],"Error: file editing is not allowed"); else fxc_Write_To_File($pagename, $fx['target'], $text); return ''; } return $text; } //}}} ////----- FoxCSVEdit 2024-04-06 -----////// //FoxEdit Globals $EditSource = $EditTarget = $EditBase = $EditSection = $EditItem = ''; //init; will be set by form $FmtPV['$EditSource'] = '$GLOBALS["EditSource"]'; $FmtPV['$EditTarget'] = '$GLOBALS["EditTarget"]'; $FmtPV['$EditSection'] ='$GLOBALS["EditSection"]'; $FmtPV['$EditItem'] ='$GLOBALS["EditItem"]'; $FmtPV['$EditBase'] ='$GLOBALS["EditBase"]'; // make csv edit form by setting page vars and loading edit form $HandleActions['foxcsvedit'] = 'fxc_Edit_Form'; function fxc_Edit_Form($pagename) { global $EditSource, $EditTarget, $EditBase, $EditSection, $EditItem, $InputValues; $args = RequestArgs($_POST); // fetch POST arguments if (empty($args['source'])) FoxAbort($pagename,"%red%'''Error:''' no source given"); //DEBUG show($args,'edit args'); // initialising variables $section = $args['section'] ?? ''; // check for file edit or page (section) edit if (preg_match('/\.(csv|txt)$/i', $args['source'], $m)) { $source = $args['source']; $EditSource = $target = $source; $csvfile = 1; // file edit flag, will be passed on at form submit to Fox processing } else { $csvfile = 0; $ss = explode('#', $args['source']); $source = ($ss[0]=='') ? $pagename : $ss[0]; if (isset($ss[1])) { $section = "#".$ss[1]; } # else $section = $source; } $EditSource = $source; $EditSection = $section; $EditItem = $idx = $args['idx'] ?? ''; if (empty($idx)) FoxAbort($pagename,"%red%'''Error:''' no idx given"); $csv = (isset($args['csv'])) ? explode(",", $args['csv']) : ''; $target = $args['target'] ?? $source; $EditTarget = MakePagename($pagename, $target); $EditBase = $base = $args['base'] ?? $EditTarget; $fulltarget = (isset($section) && strstr($section,'#')) ? $target.$section : $target; // open target page or file, get text section, set InputValues for 'text' and 'mark' controls $text = fxc_Get_Text( $pagename, $fulltarget); if (empty($text)) FoxAbort($pagename,"$[Error: cannot find edit template] $fulltarget"); // get rows array from text $text = trim($text,"\n\r"); $rows = explode("\n", $text); //get rows // get csv separator/delimiter list($sep, $args) = fxc_Set_Sep( $text, $args); if ($idx=='header') $idx = 0; $data = fxc_Parse_CSV( $text, $sep ); $rowcnt = count($data); $header = $data[0]; $multi = (isset($args['multiline'])) ? fxc_Fields($args['multiline'], $header) : array(); foreach ($header as $k=>$v) { if (preg_match('/^[\d]|[^\-a-zA-Z0-9_]/', $v, $m)) FoxAbort($pagename, "'''Error: invalid header name(s)!''' (No digits at beginning, no spaces, no punctuations)"); } $key = ($header[0]=='IDX') ? fxc_Get_Row_Key($rows, $idx, $sep) : $idx; $idxtitle = " #$idx"; $idx0 = $idx; $act = 'replace'; if (substr($idx,0,3)=='new') { $act = 'addnew'; if ($idx=='newtop') { $idx = $key; $key = 0; //top insert (below header) $idxtitle = " to top"; } if ($idx=='new') { $idx = $key; $key = $rowcnt; //bottom insert (below last) $idxtitle = " to bottom"; } if (preg_match('/new(\d+)/', $idx, $m)) $key = $m[1]; //insert new after row $key if ($header[0]=='IDX') $data[$key] = array($key); else $data[$key] = array(); } if (!array_key_exists($key, $data)) $item = array(); else $item = $data[$key]; // set InputValues for the item (csv data from row) to fill form's text fields [input defaults requests=1] foreach($item as $k => $value) { $fn = $header[$k] ?? ''; if (!isset($InputValues[$fn])) $InputValues[$fn] = trim(str_replace('$','$',htmlspecialchars($value,ENT_NOQUOTES))); } //note: we use hardcoded form as default, if no formpage is set via form= parameter if (isset($args['form'])) { $formpage = $args['form']; } //retrieve edit form from page or page section if (!empty($formpage)) { if (substr($formpage,0,1)=='#') $formpage = $pagename.$formpage; $formname = MakePagename($pagename, $formpage); if (PageExists($formname)) { $eform = RetrieveAuthSection($formname, $formpage); if(empty($eform)) FoxAbort($pagename,"$[Error: cannot find edit template] $formpage"); } } ## edit form //we got no form page, so we use hardcoded form, to edit csv row: if (empty($eform)) { $eform = "(:fox eform foxaction=csv csvact=$act csvidx=$key target=$fulltarget redirect=$base:)"; // set template $tv= ''; foreach ($header as $n) $tv .= "{\$\$".$n."}$sep"; //no space after seperator! $eform .= "(:foxtemplate \"".rtrim($tv,$sep)."\":)"; //trim trailing sep! trailing sep stays from text row! $eform .= "(:input defaults request=1:)(:input hidden csum '$[Updated CSV Table]':)(:input hidden rowcnt $rowcnt:)" ."(:input hidden csvsep $sep:)(:input hidden csvfile $csvfile:)". (isset($args['env']) ? "(:input hidden csvenv 1:)" : ''). (substr($idx0,0,3)=='new' ? "\n!!!Adding csv record " : "\n!!!Editing csv record "). ($idx0==0 ? "Header": $idxtitle)." on {$fulltarget}"; // set input fields foreach ($header as $name) { $area = (in_array($name, $multi)) ? 'area cols=40 rows=4' : ' size=40'; if (isset($InputValues[$name]) && preg_match('/\n/',$InputValues[$name])) $area = 'area cols=40 rows=4'; if (is_array($csv)) { if (in_array($name, $csv)) $eform .= "\n|| $name:||(:input text$area $name :) ||"; else $eform .= "(:input hidden $name:)"; } else { if ($name=='IDX') $eform .= "(:input hidden IDX $idx:)"; //IDX field is not for editing else $eform .= "\n|| $name:||(:input text$area $name :) ||"; } } $eform .= "\n|| ||(:input submit post '$[Save]':)". (isset($args['saveasnew']) ? "  (:input submit post2 '$[Save as New]':)(:input hidden saveasnew ".$args['saveasnew'].":)": ''). "  (:input submit cancel '$[Cancel]':) ||(:foxend eform:)"; } // make HTML editform page global $FmtV, $FoxEditForm, $PageStartFmt, $FoxPageEditFmt, $PageEndFmt; $FmtV['$FoxEditFrm'] = MarkupToHTML($pagename, $eform); $FoxPageEditFmt = '$FoxEditFrm'; $HandleEditFmt = array(&$PageStartFmt, &$FoxPageEditFmt, &$PageEndFmt); PrintFmt($pagename, $HandleEditFmt); exit; } //}}} Markup('foxcsveditform','directives','/\\(:csv-edit\\s*(.*?)\\s*:\\)/', "fxc_Edit_Button"); // make (:csv-edit ...:) form button HTML function fxc_Edit_Button ($m) { extract($GLOBALS['MarkupToHTML']); $PageUrl = PageVar($pagename, '$PageUrl'); $args = ParseArgs($m[1]); $args[''] = (array)@$args['']; $source = $args['source'] ?? array_shift($args['']); $idx = $args['idx'] ?? array_shift($args['']); $label = $args['label'] ?? array_shift($args['']); $csv = $args['csv'] ?? ''; $sep = $args['sep'] ?? ''; $env = $args['env'] ?? ''; $form = $args['form'] ?? ''; $multiline = $args['multiline'] ?? ''; $saveasnew = $args['saveasnew'] ?? ''; //title (tooltip) if (!empty($idx)||$idx==='0') { if (substr($idx,0,3)=='new') { if (isset($label)) $title = $label; else { $label = 'Add new'; $title = "$[Add new item]"; $imageurl = $GLOBALS['FoxCSVConfig']['addbuttonurl']; } } else { if ($idx=='header') { if (isset($label)) $title = $label; else { $title = "$[Edit header]"; $imageurl = $GLOBALS['FoxCSVConfig']['editbuttonurl']; } } else { if (isset($label)) $title = $label; else { $title = "$[Edit item] $idx"; $imageurl = $GLOBALS['FoxCSVConfig']['editbuttonurl']; } } } } else if (!empty($source)) $title = "$[Edit] ".$source; $title = $args['title'] ?? $title ?? ''; $target = $args['target'] ?? ''; $base = $args['base'] ?? $pagename; //return to base if(empty($label)) $label = "$[Edit]"; $inputs = array( 'source' => $source, 'idx' => $idx, 'sep' => $sep, 'env' => $env, 'csv' => $csv, 'base' => $base, 'form' => $form, 'target' => $target, 'multiline' => $multiline, 'saveasnew' => $saveasnew, ); $out = "\n
". ""; foreach($inputs as $n=>$v) if (!empty($v)) { $out .= ""; } $out .= (!empty($imageurl) ? "" : ""); $out .= "
"; return Keep(FmtPagename($out,$pagename)); } //}}} //EOF