1 #!/usr/local/bin/php -Cq
2 <?php define("EWIKICTL_VERSION", "0.3");
4 # this script needs to be located where it currently is!!
5 # you can however make a symlink into /usr/local/bin
8 #-- ewikictl is now used as commandline tool and library
11 #-- do not run below httpd
12 if ($_SERVER["SERVER_SOFTWARE"]) {
13 die("<b>ewikictl</b> is a command line tool! (you need a shell account)");
16 #-- load ewiki library / open database
17 $_SERVER["PHP_AUTH_USER"]=$_SERVER["PHP_AUTH_PW"]="localhost";
19 chdir(dirname(__FILE__));
20 foreach (array("config.php", "ewiki.php", "t_config.php") as $inc) {
21 foreach (array('./', '../') as $dir) {
22 if (file_exists("$dir$inc")) {
25 if (class_exists("ewiki_db")) break 2;
29 if (!class_exists("ewiki_db")) {
30 echo "You cannot move around this utility, it needs to be located nereby the\nother ewiki tools/ (or at least ewiki.php or some config.php)!\n";
33 @include_once("plugins/lib/upgrade.php");
38 $config = regex_getopts(
40 "help" => "/^-+(h|help)$/i",
41 "backup" => "/^-+(b|backup)$/i",
42 "all" => "/^-+(a|all)$/i",
43 "format" => "/^-+(f|format)$/i",
44 "insert" => "/^-+(insert|i|in|backdown|read|init|load|read)$/i",
45 "keep" => "/^-+(keep|hold|old|keepold|noappend|current)$/i",
46 "urlencode" => "/^-+(url|url.+code|enc|dec|win|dos|crlf|backslash)$/i",
47 "holes" => "/^-+(holes|strip|empty|air)$/i",
48 "dest" => "/^-+(dest|destination|path|d|dir|source|from|to)$/i",
49 "force" => "/^-+force/i",
50 "db" => "/^-+(db|database)$/i",
51 "ls" => "/^-+(ls|list|ll|la)$/i",
52 "file" => "/^-+(page|file|name|pagename|id)$/i",
53 "chmod" => "/^-+(chmod|ch|mode?|set|flags|flag)\s.+$/i",
54 "unlink" => "/^-+(unlink|purge|remove|rm|del)$/i",
55 "rename" => "/^-+(rename|move|mv|ren|cp)$/i",
59 #-- db connect, if necessary
61 preg_match('/^([^:@\/]+)[:]?([^:@\/]*?)[@]?([^:@\/]*?)\/(.+)$/', $config["db"], $uu);
62 $user = $uu[1]; $pw = $uu[2];
63 $host = $uu[3]; $dbname = $uu[4];
64 mysql_connect($host, $user, $pw);
65 mysql_query("USE $dbname");
74 if ($config["help"]) {
77 usage: ewikictl [--command param] [--option2 ...] [page names ...]
78 --help -h shows up this help screen
79 --backup -b save pages from database
80 --all -a all page versions (not only newest)
81 --format -f file format for --backup, --holes and --insert
82 -f plain only page content into the text files
83 -f flat (default) page files in the db_flat_files format
84 -f fast files in the binary db_fast_files format
85 -f meta plain format + companion .meta files
86 -f xml in xml-like files
87 -f sql mysql INSERT statement script (only for --backup)
88 --holes create page version holes in the database (but save the
89 deleted file versions, if --backup is given)
90 --holes 2..-10 is the default and tells ewikictl to purge
91 page versions 2 until 10 before the last
92 --insert -i read files into database, requires --all if multiple versions
93 exist; pages in the database won't be overwritten, so you may
94 need to do an "--unlink *" before the actual --insert,
95 the --format option is important for this!
96 --insert <filename> insert just the given file instead of a whole dir
97 --keep do not --insert page if already in the database (only single
98 page version mode - e.g. no version numbers in filenames)
99 --dest <dir> specifes the in/output directory (defaults to './backup-%c'
100 for --backup, and './holes' for --holes)
101 --urlencode create/read backup filenames assuming Win4 restrictions
102 --force proceed after warnings and error messages
103 --db user:pw@host/dbname - if the ewiki.php couldn't be loaded automatically
106 --list -ls show pages
107 --chmod NN set page flags to the given (decimal/0xHex/Oct) value,
108 or use a mix of page flag names (TEXT,BIN,DISABLED,HIDDEN,
109 PART,READONLY,RO,WRITE,RW,APPEND,MINOR,EXEC) to add/set/revoke values:
110 --chmod +TXT,HTML,-OFF or --chmod =SYS or --chmod 0x001
111 --unlink -rm delete specified page (all versions), can use *
112 --rename -mv assign a new name to a page --mv OldPage NewName
113 Page names to work on (with one of the above operations) can be specified as
114 standard arguments or via a --page or --file parameter.
120 elseif ($config["holes"]) {
122 (empty($config["dest"])) and ($dest = "holes");
129 elseif ($config["insert"]) {
135 elseif ($config["backup"]) { #--------------------------------------
140 elseif ($fn = $config["ls"]) {
146 #-- change page database flags
147 elseif ($config["chmod"]) {
149 $files = array_merge(
151 fn_from(array("file"))
154 die("no --file or page name specifed!\nplease see --help\n");
158 "/(T[EXT]+|WI[KI]*|RE[SET]*|DEF[AULT]*)/" => EWIKI_DB_F_TEXT,
159 "/BIN/" => EWIKI_DB_F_BINARY,
160 "/SYS/" => EWIKI_DB_F_PART,
161 "/(DIS?|OFF)/" => EWIKI_DB_F_DISABLED,
162 "/P(ART?|T)/" => EWIKI_DB_F_PART,
163 "/HT[ML]*/" => EWIKI_DB_F_HTML,
164 "/(RO|READ)/" => EWIKI_DB_F_READONLY,
165 "/(WR|RW)/" => EWIKI_DB_F_WRITEABLE,
166 "/AP/" => EWIKI_DB_F_APPENDONLY,
171 "/EX/" => EWIKI_DB_F_EXEC,
172 "/(HI[DDEN]*|H$|INV[ISIBLE]*)/" => EWIKI_DB_F_HIDDEN,
173 "/(MIN?)/" => EWIKI_DB_F_HIDDEN,
176 #-- walk through given page names
177 foreach ($files as $id) {
179 $data = ewiki_db::GET($id);
180 if ($data["version"]) {
182 #-- decode +"TXT,HTML" or "15" strings
183 $mode = strtoupper($config["chmod"]);
184 if (substr($mode, 0, 2) == "0X") {
185 $flags = base_convert(substr($mode, 2), 16, 10);
187 elseif (($mode[0]=="0") || ($mode[0]=="O")) {
188 $flags = octdec(substr($mode, 1));
190 elseif (preg_match('/^[0-9]+$/', $mode)) {
195 $flags = $data["flags"];
197 preg_match_all('/([-=+,:;])(\w+)/', "=$mode", $uu);
198 foreach ($uu[2] as $i=>$str) {
199 switch ($uu[1][$i]) {
209 foreach ($fnames as $find=>$val) {
210 if (preg_match($find, $str) || ($str > 0) && ($val = 1 << $str)) {
212 $flags &= (0x7FFFFFFF ^ $val);
219 }#-- foreach(+FLAG,-FLAG)
221 $data["flags"] = $flags;
224 $data["author"] = ewiki_author("ewikictl");
225 $data["lastmodified"] = time();
227 ewiki_db::WRITE($data);
229 #-- say what's going on
230 echo "new page flags are 0x" . str_pad(dechex($data["flags"]), 4, "0", STR_PAD_LEFT) . "\n";
240 elseif ($file = $config["unlink"]) {
242 $regex = preg_quote($file);
243 $regex = str_replace("\\\\*", ".*", $regex);
245 if (($file == "*") || !strlen($regex)) {
246 chk_forced("don't want to delete all files");
249 $result = ewiki_db::GETALL(array());
250 while ($row = $result->get()) {
253 if (($file != $id) && (!preg_match("\007$regex\007i", $id))) {
257 echo "[DELETE]
\e[1;31m" . $id . "
\e[0;37m\n";
260 for ($v=1; $v<=$row["version"]; $v++) {
261 ewiki_db::DELETE($id, $v);
268 #-- page moving / renaming
269 elseif ($file = $config["rename"]) {
273 echo "rename from $fn1 to $fn2\n";
274 if ($data = ewiki_db::GET($fn1)) {
275 $ver = $data["version"];
278 chk_forced("source page does not exist");
280 if (ewiki_db::GET($fn2)) {
281 chk_forced("destination page name already exists");
282 echo "(won't overwrite existing versions)\n";
285 #-- from current to earliest version
288 if ($data = ewiki_db::GET($fn1, $ver)) {
290 if ($ok = ewiki_db::WRITE($data)) {
291 ewiki_db::DELETE($fn1, $ver);
299 echo "moved $n1 versions correctly ($n0 errors/version doublettes)\n";
308 echo "ewikictl: please use --help\n";
313 #----------------------------------------------------------------------
315 function func_ls($fn = 1) {
317 $result = ewiki_db::GETALL(array());
320 echo $result->count()." pages\n";
323 while ($row = $result->get()) {
326 if (($fn != 1) & ($fn != $id)) {
329 $row = ewiki_db::GET($id);
332 . ($row["flags"] & EWIKI_DB_F_DISABLED ? "-" : "r")
333 . ($row["flags"] & EWIKI_DB_F_READONLY ? "-" : "w")
334 . ($x = ($row["flags"] & EWIKI_DB_F_BINARY) ? "x" : "-")
336 . ($row["flags"] & EWIKI_DB_F_TEXT ? "t" : "-")
338 . ($row["flags"] & EWIKI_DB_F_WRITEABLE ? "w" : "-")
339 . ($row["flags"] & EWIKI_DB_F_HTML ? "h" : "-")
342 echo str_pad($row["version"], 4, " ", STR_PAD_LEFT);
344 echo " " . str_pad(substr($row["author"], 0, 16), 16, " ");
346 echo str_pad(strlen($row["content"]), 10, " ", STR_PAD_LEFT);
348 echo str_pad(strftime("%b %e %H:%M", $row["lastmodified"]), 14, " ", STR_PAD_LEFT);
350 if ($row["flags"] & EWIKI_DB_F_BINARY) {
351 echo "
\e[1;32m " . $id . "
\e[0;37m";
361 #----------------------------------------------------------------------
363 function command_insert() {
364 global $config, $allv, $save_format, $dest;
366 if ($config["backup"] && !$config["force"]) {
367 die("cannot do --backup AND --insert at the same time!\n");
372 $versioned_files = 0;
375 if (($fn = $config["insert"]) != "1") { #-- just one file
380 $dh = opendir($dest);
381 while ($fn = readdir($dh)) {
387 foreach ($dir as $fn) {
392 if ((DIRECTORY_SEPARATOR=="/") && (!$config["urlencode"])) {
393 $id = strtr($fn, "\\", "/");
396 $id = urldecode($fn);
401 if (preg_match('/\.\d+$/', $id)) {
408 if ((!$allv) && ($versioned_files * 2 >= count($files))) {
409 echo "WARNING: the input files are versioned ones, you must give the --all\noption, or this will probably lead to errors.\n";
410 if (!$config["force"]) {
411 die("I would proceed with --force\n");
416 foreach ($files as $fn=>$id) {
419 $p = strrpos($id, ".");
420 $ver = substr($id, $p + 1);
421 $id = substr($id, 0, $p);
422 if ((!$p) || empty($id) || empty($fn) || ($ver <= 0)) {
423 echo "[SKIP] $id ($fn)\n";
428 if ($config["keep"]) {
432 $current = ewiki_db::GET($id);
433 $ver = $current["version"] + 1;
437 $content = read_file($fn);
439 switch ($save_format) {
442 if (strstr($id, "://")) {
443 $flags = EWIKI_DB_F_BINARY;
444 $meta = array("Content-Type" => "application/octet-stream");
447 $flags = EWIKI_DB_F_TEXT;
452 "content" => $content,
455 "created" => filectime("$dest/$fn"),
456 "lastmodified" => filemtime("$dest/$fn"),
457 "author" => ewiki_author("ewikictl"),
465 $data = read_meta_format_dbff($content);
469 die("FAILURE: unsupported --format!\n");
472 if ($uu = $data["id"]) {
476 if (empty($data["content"]) || empty($data["flags"])) {
477 echo "[EMPTY] $id ($fn)\n";
482 $res = ewiki_db::WRITE($data);
484 echo "[OK] $id ($fn)\n";
487 echo "[ERROR] $id ($fn)\n";
495 #----------------------------------------------------------------------
498 global $config, $allv, $save_format, $dest;
500 $vers = $config["holes"];
501 if (preg_match('/^(\d+)[-.:_]+(\d+)$/', trim($vers), $uu)) {
502 $vers = array($uu[1], $uu[2]);
504 echo "WARNING: you should never purge version 1, as it sometimes (dbff) holds\ncontrol data!\n";
505 if (!$control["force"]) {
506 die("\nuse --force if you really want this\n");
510 die("REFUSE to delete the latest page versions.\n");
514 $vers = array(2, 10);
516 echo "will remove page versions ".$vers[0]." until -".$vers[1]."\n";
518 $result = ewiki_db::GETALL(array());
519 while ($row = $result->get()) {
523 $ver1 = $row["version"] - $vers[1];
526 for ($v=$ver0; $v <= $ver1; $v++) {
528 if ($config["backup"]) {
530 $delete = backup($id, $v);
534 echo "deleting $id.$v\n";
535 ewiki_db::DELETE($id, $v);
544 #----------------------------------------------------------------------
546 function command_backup() {
548 global $dest, $allv, $config;
550 if (!file_exists($dest)) {
554 $result = ewiki_db::GETALL(array());
555 while ($row = $result->get()) {
558 $data = ewiki_db::GET($id);
559 $ver0 = $verZ = $data["version"];
560 if ($allv) { $ver0 = 1; }
563 for ($v = $verZ; $v >= $ver0; $v--) {
573 function backup($id, $v) {
574 global $allv, $save_format, $dest, $config;
576 $save = ewiki_db::GET($id, $v);
580 $content = $save["content"];
582 #-- base filename for current page
584 if ((DIRECTORY_SEPARATOR=="/") && (!$config["urlencode"])) {
585 $filename = strtr($filename, '/', '\\');
588 $filename = urlencode($filename);
591 $filename .= "." .$save["version"];
595 switch ($save_format) {
598 save_file($filename . ".meta", save_meta_format_flat($save));
601 save_file($filename, $content);
605 $content = save_meta_format_flat($save) . $content;
606 save_file($filename, $content);
610 save_file($filename, save_meta_format_fast($save));
614 $content = save_meta_format_xml($save, "BACKUP");
615 save_file($filename, $content);
619 save_file($filename . ".meta", save_meta_format_xml($save));
620 save_file($filename, $content);
625 save_file($filename . ".sql", save_meta_format_sql($save), ($save_format=="mysql"?"REPLACE":"INSERT"));
629 touch("$dest/$filename", $save["lastmodified"]);
635 #----------------------------------------------------------------------
637 function xml____entities($s) {
638 $map = array("&"=>"&", ">"=>">", "<"=>"<", '"'=>""", "'"=>"'", "\000"=>"�");
639 $s = strtr($s, $map);
643 function save_file($filename, $content) {
644 if (is_array($content)) { $content = $content["content"]; }
645 $f = fopen($filename = $GLOBALS["dest"] . "/" . $filename, "wb");
646 fwrite($f, $content);
650 function save_meta_format_xml($data, $t = "META") {
651 if ($t=="META") unset($data["content"]);
652 $xml = "<!DOCTYPE EWIKI_$t>\n<EWIKI_$t>\n";
653 foreach ($data as $field=>$value) {
654 $xml .= " <$field>" . xml____entities($value) . "</$field>\n";
656 $xml .= "</EWIKI_$t>\n";
660 function save_meta_format_flat($data) {
661 unset($data["content"]);
663 foreach ($data as $field=>$value) {
664 $flat .= "$field: " . str_replace("\n", EWIKI_DBFILES_NLR, $value) . "\015\012";
670 function save_meta_format_fast($data) {
671 $data = serialize($data);
672 if (function_exists("gzencode")) {
673 $data = gzencode($data);
677 #----------------------------------------------------------------------
681 #------------------------------------------------------------------------
683 function read_file($filename) {
684 $f = fopen($GLOBALS["dest"] . "/" . $filename, "rb");
685 $content = fread($f, 1<<21-1);
690 #------------------------------------------------------------------------
692 function read_meta_format_dbff($ct) {
696 if (function_exists("gzdecode") && ($uu = gzdecode($ct))) {
697 if (($uu = unserialize($uu)) && is_array($uu)) {
702 $p = strpos($ct, "\012\015\012");
703 $p2 = strpos($ct, "\012\012");
704 if ((!$p2) || ($p) && ($p < $p2)) {
710 $r["content"] = substr($ct, $p);
711 $ct = substr($ct, 0, $p);
713 foreach (explode("\012", $ct) as $h) {
715 $r[trim(strtok($h, ":"))] = str_replace(EWIKI_DBFILES_NLR, "\n", trim(strtok("\000")));
722 #------------------------------------------------------------------------
724 #------------------------------------------------------------------------
726 function chk_forced($err) {
728 if ($config["forced"]) {
729 echo "$err: but --force'd to proceed\n";
732 die("$err: giving up, use --force next try\n");
736 #------------------------------------------------------------------------
739 function set_options_global()
741 global $dest, $allv, $save_format, $lib, $config;
743 ($dest = $config["dest"]) and ($dest != "1")
744 or (is_dir($dest = fn_from(array("backup", "insert", "holes"))))
745 or (!$lib) && ($dest = strftime("backup-%G%m%d%H%M", time()));
747 $allv = $config["all"];
749 ($save_format = strtolower($config["format"])) and ($save_format != "1")
750 or ($save_format = "flat");
753 #----------------------------------------------------------------------
755 function fn_from($in, $config=false) {
756 if ($config === false) {
757 $config = $GLOBALS["config"];
759 foreach ($in as $i) {
760 if (($r = $config[$i]) && ($r !== 1)) {
766 function filenames() {
769 for ($n=0; $n<1000; $n++) {
770 if (!isset($config[$n])) break;
776 #------------------------------------------------------------------------
778 function regex_getopts($regexopts) {
779 if (empty($_SERVER)) {
780 $_SERVER = $GLOBALS["HTTP_SERVER_VARS"];
782 if (!empty($GLOBALS["argc"])) {
783 $_SERVER["argc"] = $GLOBALS["argc"];
784 $_SERVER["argv"] = $GLOBALS["argv"];
787 for ($n = 1; $n < $_SERVER["argc"]; $n++) {
788 foreach ($regexopts as $opts_id => $optsregex) {
790 $next = @$_SERVER["argv"][$n+1];
791 if (preg_match($optsregex, $_SERVER["argv"][$n]." ".$next)) {
792 $opts[$opts_id] = $next;
795 elseif (preg_match($optsregex, $_SERVER["argv"][$n])) {
797 if ($next && ($next[0] != "-")) {
801 $opts[$opts_id] = $value;
805 $opts[] = $_SERVER["argv"][$n];
809 #-------------------------------------------------------------------------