big commit: implemented sort-of 2-phase (3-phase actually) safe commit in FP; first entry is saved as draft, then content is moved along when indices have been succesfully updated; this way if btree fails, entry is anyway kept in a safe place; added a few cosmetic changes, a bunch of new, more descriptive language strings for the entry panel; added a "you are editing a draft" notice in the panel; hope you will like it :)

This commit is contained in:
real_nowhereman 2009-02-03 16:53:52 +00:00
parent 9413e4dee7
commit d5e666a1a1
11 changed files with 232 additions and 125 deletions

View File

@ -24,6 +24,7 @@
); );
var $events = array('save', 'preview', 'savecontinue'); var $events = array('save', 'preview', 'savecontinue');
var $draft = false;
function _makePreview($arr, $id=null) { function _makePreview($arr, $id=null) {
@ -32,24 +33,43 @@
$arr['content'] = apply_filters('content_save_pre', $arr['content']); $arr['content'] = apply_filters('content_save_pre', $arr['content']);
} }
if ($this->draft || $this->draft = draft_exists($this->id)) {
if (isset($arr['categories'])
&& is_array($arr['categories']) && !in_array('draft', $arr['categories']) ) {
$arr['categories'][] = 'draft';
} else {
$arr['categories'][] = 'draft';
}
}
// unfiltered content (for editing)
$this->smarty->assign('post', $arr); $this->smarty->assign('post', $arr);
if (THEME_LEGACY_MODE) { if (THEME_LEGACY_MODE) {
theme_entry_filters($arr, $id); theme_entry_filters($arr, $id);
} }
$arr = array_change_key_case($arr, CASE_LOWER); // content for preview
$this->smarty->assign('entry', $arr); $this->smarty->assign('entry', $arr);
$this->smarty->assign('preview', true); $this->smarty->assign('preview', true);
} }
function makePageTitle($title, $sep) { function makePageTitle($title, $sep) {
global $lang; global $lang, $panel;
if ($this->draft) {
$this->smarty->append(
'warnings',
$lang['admin']['entry']['write']['msgs']['draft']
);
}
return "$title $sep {$lang['admin']['entry']['write']['head']}"; return "$title $sep {$lang['admin']['entry']['write']['head']}";
} }
function draft_class($string) {
return "$string draft";
}
function _getCatsFlags() { function _getCatsFlags() {
@ -90,6 +110,7 @@
$this->_getCatsFlags(); $this->_getCatsFlags();
add_filter('wp_title', array(&$this, 'makePageTitle'), 10, 2); add_filter('wp_title', array(&$this, 'makePageTitle'), 10, 2);
if ($this->draft) add_filter('admin_body_class', array(&$this, 'draft_class'));
} }
@ -101,12 +122,14 @@
$author = user_get(); $author = user_get();
$arr['author'] = $author['userid']; $arr['author'] = $author['userid'];
$arr['date'] = !empty($_POST['timestamp'])?$_POST['timestamp']:date_time(); $arr['date'] = !empty($_POST['timestamp'])?$_POST['timestamp']:date_time();
$cats = !empty($_POST['cats'])?$_POST['cats']:array(); $cats = !empty($_POST['cats'])?$_POST['cats']:array();
$flags = !empty($_POST['flags'])?$_POST['flags']:array(); $flags = !empty($_POST['flags'])?$_POST['flags']:array();
$catids = array_merge(array_keys($flags), array_keys($cats)); $catids = array_merge(array_keys($flags), array_keys($cats));
$this->draft = isset($flags['draft']);
if ($catids) if ($catids)
$arr['categories'] = $catids; $arr['categories'] = $catids;
@ -119,25 +142,25 @@
$id = $this->id; $id = $this->id;
$data = $this->_getposteddata(); $data = $this->_getposteddata();
if (isset($data['categories']) && in_array('draft', $data['categories'])) { if ($this->draft) {
$success=draft_save($data, $id, true);
$success=draft_save($data, $id); $this->smarty->assign('success', $success? 1 : -1 );
} else { } else {
/* anyway issued */
draft_to_entry($id);
$success=entry_save($data, $id); $success=entry_save($data, $id);
$this->smarty->assign('success', is_numeric($success)? $success : 1 );
} }
if ($success) sess_remove('entry'); // if ($success) sess_remove('entry');
$this->smarty->assign('success',$success? 1:-1);
if ($do_preview) if ($do_preview)
$this->_makePreview($data); $this->_makePreview($data);
if ($success<0) {
$this->main();
return PANEL_NOREDIRECT;
}
return 1; return 1;
} }
@ -151,6 +174,7 @@
$this->_getCatsFlags(); $this->_getCatsFlags();
add_filter('wp_title', array(&$this, 'makePageTitle'), 10, 2); add_filter('wp_title', array(&$this, 'makePageTitle'), 10, 2);
if ($this->draft) add_filter('admin_body_class', array(&$this, 'draft_class'));
return 0; return 0;
@ -164,7 +188,7 @@
$this->_getCatsFlags(); $this->_getCatsFlags();
add_filter('wp_title', array(&$this, 'makePageTitle'), 10, 2); add_filter('wp_title', array(&$this, 'makePageTitle'), 10, 2);
if ($this->draft) add_filter('admin_body_class', array(&$this, 'draft_class'));
} }

View File

@ -55,7 +55,7 @@
$id=basename($file,EXT); $id=basename($file,EXT);
$arr=entry_parse($id, true); $arr=entry_parse($id, true);
echo "[POST] $id => {$arr['SUBJECT']}\n"; echo "[POST] $id => {$arr['subject']}\n";
$this->index->add($id, $arr); $this->index->add($id, $arr);
return 0; return 0;

View File

@ -985,7 +985,7 @@ class BPlusTree_Node {
#d(implode(",",$this->keys)); #d(implode(",",$this->keys));
#$place = array_search($key, $this->keys); #$place = array_search($key, $this->keys);
$place = BPT_bisect($this->keys, $key, 0, $this->validkeys); $place = BPT_bisect($this->keys, $key, 0, $this->validkeys);
if (@$this->keys[$place-1] == $key) { if ($this->keys[$place-1] == $key) {
return $this->indices[$place-1]; return $this->indices[$place-1];
} else { } else {
if ($loose) { if ($loose) {
@ -2539,28 +2539,48 @@ class SBPlusTree extends BPlusTree {
$this->maxstring = $maxstring; $this->maxstring = $maxstring;
} }
function startup() {
fwrite($this->stringfile, 'BPTSTRINGS');
return parent::startup();
}
function getstring($seek) { function getstring($seek) {
fseek($this->stringfile, $seek); fseek($this->stringfile, $seek);
$s = fread($this->stringfile, $this->maxstring); $s = fread($this->stringfile, $this->maxstring);
return rtrim($s); return rtrim($s);
} }
function setstring($s) { function setstring($s, $key) {
fseek($this->stringfile, 0, SEEK_END); $seek = $this->has_key($key);
$seek = ftell($this->stringfile); if (!is_numeric($seek)) {
fseek($this->stringfile, 0, SEEK_END);
$seek = ftell($this->stringfile);
} else {
fseek($this->stringfile, $seek);
}
// nul-pad string // nul-pad string
if (strlen($s>$this->maxstring))
$x = substr($s, 0, $this->maxstring);
$x = str_pad($s, $this->maxstring, chr(0)); $x = str_pad($s, $this->maxstring, chr(0));
fwrite($this->stringfile, $x); fwrite($this->stringfile, $x);
return $seek; return $seek;
} }
function getitem(&$key, $loose=false) { function getitem(&$key, $loose=false) {
$seek = parent::getitem($key, $loose); $seek = $this->has_key($key, $loose);
return $seek!==false? $this->getstring($seek) : false; return is_numeric($seek)? $this->getstring($seek) : false;
}
/**
* @param $key target key
* @returns int seek point if key exists, 0 otherwise
*/
function has_key($key, $loose=false) {
return @parent::getitem($key, $loose);
} }
function setitem($key, $val) { function setitem($key, $val) {
$seek = $this->setstring($val); $seek = $this->setstring($val, $key);
parent::setitem($key, $seek); parent::setitem($key, $seek);
return $seek; return $seek;
} }

View File

@ -64,7 +64,7 @@
$entry = io_load_file($fname); $entry = io_load_file($fname);
$entry = array_change_key_case(utils_kexplode($entry)); $entry = utils_kexplode($entry);
if (!isset($entry['categories'])) if (!isset($entry['categories']))
$entry['categories'] = array(); $entry['categories'] = array();
else else
@ -76,7 +76,7 @@
} }
function draft_save($entry, $id=null, $update_date=false) { function draft_save($entry, $id=null, $update_index = false, $update_date=false) {
if (!$id) { if (!$id) {
$id = bdb_idfromtime('entry', $entry['date']); $id = bdb_idfromtime('entry', $entry['date']);
@ -90,28 +90,22 @@
// move collateral files // move collateral files
@rename($ed, $dd); @rename($ed, $dd);
// delete normal entry if ($update_index) {
fs_delete($ed.EXT); // delete normal entry
fs_delete($ed.EXT);
// remove from normal flow // remove from normal flow
$o =& entry_init(); $o =& entry_init();
$o->delete($id); $o->delete($id, null);
}
} }
$entry['content'] = apply_filters('content_save_pre', $entry['content']); $entry = entry_prepare($entry);
$entry['subject'] = apply_filters('title_save_pre', $entry['subject']); if ($entry['categories'])
$entry['categories']=implode(',', $entry['categories']);
$entry = array_change_key_case($entry, CASE_UPPER); else unset($entry['categories']);
if (isset($entry['CATEGORIES'])) {
if (is_array($entry['CATEGORIES']))
$entry['CATEGORIES'] = implode(',',$entry['CATEGORIES']);
else
trigger_error("Failed saving draft. Expected 'categories' to be
an array, found " . gettype($entry['CATEGORIES']), E_USER_ERROR);
}
$string = utils_kimplode($entry); $string = utils_kimplode($entry);
@ -136,9 +130,6 @@
function draft_exists($id) { function draft_exists($id) {
if (!user_loggedin())
return false;
$dir = draft_dir($id); $dir = draft_dir($id);
if (!$dir) if (!$dir)
return false; return false;

View File

@ -100,15 +100,19 @@
return $this->indices[$cat]; return $this->indices[$cat];
} }
function add($id, $entry, $del = array()) { function add($id, $entry, $del = array(), $update_title = true) {
$key = entry_idtokey($id); $key = entry_idtokey($id);
$val = $entry['SUBJECT']; $val = $entry['subject'];
$main =& $this->get_index(); $main =& $this->get_index();
$seek = $main->setitem($key, $val); $seek = null;
if (!$update_title)
if (isset($entry['CATEGORIES']) && is_array($entry['CATEGORIES'])) { $seek = $main->has_key($key, $val);
foreach ($entry['CATEGORIES'] as $cat) {
if (is_numeric($seek))
$seek = $main->setitem($key, $val);
if (isset($entry['categories']) && is_array($entry['categories'])) {
foreach ($entry['categories'] as $cat) {
if (!is_numeric($cat)) continue; if (!is_numeric($cat)) continue;
$this_index =& $this->get_index($cat); $this_index =& $this->get_index($cat);
$this_index->setitem($key, $seek); $this_index->setitem($key, $seek);
@ -117,6 +121,7 @@
if ($del) { if ($del) {
foreach($del as $cat) { foreach($del as $cat) {
// echo 'DEL '. $cat,"\n";
$this_index =& $this->get_index($cat); $this_index =& $this->get_index($cat);
$this_index->delitem($key); $this_index->delitem($key);
} }
@ -126,18 +131,19 @@
} }
function delete($id) { function delete($id, $entry) {
$key = entry_idtokey($id); $key = entry_idtokey($id);
$main =& $this->get_index(); $main =& $this->get_index();
$main->delitem($key); $main->delitem($key);
$entry = entry_parse($id);
if (isset($entry['categories']) && is_array($entry['categories'])) { if (isset($entry['categories']) && is_array($entry['categories'])) {
foreach ($entry['categories'] as $cat) { foreach ($entry['categories'] as $cat) {
if (!is_numeric($cat)) continue; if (!is_numeric($cat)) continue;
$this_index =& $this->get_index($cat); $this_index =& $this->get_index($cat);
$this_index->delitem($key); if ($this_index->has_key($key))
$this_index->delitem($key);
} }
} }
@ -202,10 +208,10 @@
function add($id, $val) { function add($id, $val) {
$this->_list[$id]=array('subject' => $val['SUBJECT'], $this->_list[$id]=array('subject' => $val['subject'],
'categories' => 'categories' =>
(isset($val['CATEGORIES'])? (isset($val['categories'])?
$val['CATEGORIES'] : array())); $val['categories'] : array()));
return $this->save(); return $this->save();
} }
@ -424,11 +430,14 @@
return file_exists($f)? $f : false; return file_exists($f)? $f : false;
} }
function entry_dir($id) { function entry_dir($id, $month_only = false) {
if (!preg_match('|^entry[0-9]{6}-[0-9]{6}$|', $id)) if (!preg_match('|^entry[0-9]{6}-[0-9]{6}$|', $id))
return false; return false;
$date = date_from_id($id); $date = date_from_id($id);
$f = CONTENT_DIR . "{$date['y']}/{$date['m']}/$id"; if ($month_only)
$f = CONTENT_DIR . "{$date['y']}/{$date['m']}/";
else
$f = CONTENT_DIR . "{$date['y']}/{$date['m']}/$id";
return $f; return $f;
@ -450,18 +459,18 @@
// propagates the error if entry does not exist // propagates the error if entry does not exist
if (isset($arr['CATEGORIES']) && // fix to bad old behaviour: if (isset($arr['categories']) && // fix to bad old behaviour:
(trim($arr['CATEGORIES']) != '')) { (trim($arr['categories']) != '')) {
$cats = (array)explode(',',$arr['CATEGORIES']); $cats = (array)explode(',',$arr['categories']);
$arr['CATEGORIES'] = (array) $cats; $arr['categories'] = (array) $cats;
} else $arr['CATEGORIES'] = array(); } else $arr['categories'] = array();
// if (!is_array($arr['CATEGORIES'])) die(); // if (!is_array($arr['categories'])) die();
if (!isset($arr['AUTHOR'])) { if (!isset($arr['AUTHOR'])) {
global $fp_config; global $fp_config;
@ -469,7 +478,7 @@
} }
if ($raw) return $arr; if ($raw) return $arr;
return array_change_key_case($arr, CASE_LOWER); return $arr;
} }
@ -620,62 +629,122 @@
} }
function entry_save($entry_cont, $id=null, $update_index = true) {
// @TODO : check against schema ?
function entry_prepare($entry) { // prepare for serialization
global $post; global $post;
$obj =& entry_init(); // fill in missing value
if (!isset($entry['date'])) {
if (!isset($entry_cont['date'])) { $entry['date']=date_time();
$entry_cont['date']=date_time();
} }
$post = $entry_cont;
$entry = array_change_key_case($entry_cont, CASE_UPPER); // import into global scope
$post = $entry;
// apply *_pre filters
$entry['content'] = apply_filters('content_save_pre', $entry['content']);
$entry['subject'] = apply_filters('title_save_pre', $entry['subject']);
// prepare for serialization
if (isset($entry['categories'])) {
if (!is_array($entry['categories'])) {
trigger_error("Expected 'categories' to be an array, found "
. gettype($entry['categories']), E_USER_WARNING);
$entry['categories'] = array();
}
} else { $entry['categories'] = array(); }
return $entry;
}
function entry_save($entry, $id=null, $update_index = true) {
// PHASE 1 : prepare entry
if (!$id) { if (!$id) {
$id = bdb_idfromtime(BDB_ENTRY, $entry['DATE']); if (!@$entry['date']) $entry['date'] = date_time();
$id = bdb_idfromtime(BDB_ENTRY, $entry['date']);
} }
do_action('publish_post', $id, $entry_cont);
$f = bdb_idtofile($id);
$entry['CONTENT'] = apply_filters('content_save_pre', $entry['CONTENT']);
$entry['SUBJECT'] = apply_filters('title_save_pre', $entry['SUBJECT']);
$del = array(); do_action('publish_post', $id, $entry);
if ($arr = entry_parse($id)) {
if (isset($entry['CATEGORIES']) && is_array($entry['CATEGORIES'])) // PHASE 2 : Store
$del = array_diff($arr['categories'], $entry['CATEGORIES']);
// secure data as DRAFT
$ret = draft_save($entry, $id);
if ($ret === false) {
return -1; // FAILURE: ABORT
} }
$ok = ($update_index) ? $obj->add($id, $entry, $del) : true;
// PHASE 3 : Update index
$delete_cats = array();
$all_cats = @$entry['categories'];
$update_title = false;
if ($old_entry = entry_parse($id)) {
if ($all_cats) {
$delete_cats = array_diff($old_entry['categories'], $all_cats);
}
$all_cats = $all_cats? array_merge($all_cats, $old_entry['categories']) : $old_entry['categories'];
$update_title = $entry['subject'] != $old_entry['subject'];
}
/*
echo 'old';
print_r($old_entry['categories']);
echo 'new';
print_r($entry['categories']);
echo 'del';
print_r($delete_cats);
echo 'all';
print_r($all_cats);
*/
$INDEX =& entry_init();
$ok = ($update_index) ? $INDEX->add($id, $entry, $delete_cats, $update_title) : true;
// PHASE 4 : index updated; let's move back the entry
if ($ok) { if ($ok) {
$entryd = entry_dir($id, true);
$entryf = $entryd.$id.EXT;
$draftf = draft_exists($id);
if ($draftf === false) { // this should never happen!
if ($update_index) {
$INDEX->delete($id, $all_cats);
}
return -2;
}
fs_delete($entryf);
if (isset($entry['CATEGORIES'])) { fs_mkdir($entryd);
$ret = rename($draftf, $entryf);
if (is_array($entry['CATEGORIES']))
$entry['CATEGORIES'] = implode(',',$entry['CATEGORIES']); if (!$ret) {
else if (draft_exists($id)) {
trigger_error("Failed saving entry. Expected 'categories' to be // rollback changes in the index
an array, found " . gettype($entry['CATEGORIES']), E_USER_ERROR); // (keep the draft file)
if ($update_index) {
$INDEX->delete($id, $all_cats);
}
return -3;
} else { echo 'zomg bacon';
return -2;
}
} else {
// SUCCESS : delete draft, move comments along
draft_to_entry($id);
return $id;
} }
$str = utils_kimplode($entry);
if (!io_write_file($f, $str)) {
if ($update_index)
$obj->delete($id, $entry);
return false;
} else return $id;
} }
return false; return -4;
} }

View File

@ -773,7 +773,7 @@
if (isset($params['content']) && is_array($params['content']) && $params['content']) { if (isset($params['content']) && is_array($params['content']) && $params['content']) {
//foreach ($params['entry'] as $k => $val) //foreach ($params['entry'] as $k => $val)
$smarty->assign(array_change_key_case($params['content'], CASE_LOWER)); $smarty->assign($params['content']);
return $content; return $content;
} }

View File

@ -26,7 +26,7 @@
function static_parse($id) { function static_parse($id) {
if ($fname=static_exists($id)) { if ($fname=static_exists($id)) {
$entry = io_load_file($fname); $entry = io_load_file($fname);
return array_change_key_case(utils_kexplode($entry)); return (utils_kexplode($entry));
} }
return array(); return array();
} }
@ -35,7 +35,6 @@
function static_save($entry, $id, $oldid=null) { function static_save($entry, $id, $oldid=null) {
$fname = STATIC_DIR . $id . EXT; $fname = STATIC_DIR . $id . EXT;
$entry = array_change_key_case($entry, CASE_UPPER);
$entry['CONTENT'] = apply_filters('content_save_pre', $entry['CONTENT']); $entry['CONTENT'] = apply_filters('content_save_pre', $entry['CONTENT']);
$entry['SUBJECT'] = apply_filters('title_save_pre', $entry['SUBJECT']); $entry['SUBJECT'] = apply_filters('title_save_pre', $entry['SUBJECT']);
@ -106,7 +105,7 @@
if (isset($params['content']) && is_array($params['content']) && $params['content']) { if (isset($params['content']) && is_array($params['content']) && $params['content']) {
//foreach ($params['entry'] as $k => $val) //foreach ($params['entry'] as $k => $val)
$smarty->assign(array_change_key_case($params['content'], CASE_LOWER)); $smarty->assign($params['content']);
return $content; return $content;
} }

View File

@ -85,8 +85,9 @@ if (!function_exists('fnmatch')) {
function utils_kexplode($string, $delim='|', $keyupper=true) { function utils_kexplode($string, $delim='|', $keyupper=true) {
$arr = array(); $arr = array();
$string = trim($string); $string = trim($string);
$arr[strtok($string, $delim)] = strtok($delim); $k = strtolower(strtok($string, $delim));
$arr[$k] = strtok($delim);
while (( $k = strtok($delim) ) !== false) { while (( $k = strtok($delim) ) !== false) {
if ($keyupper && !preg_match('/[A-Z-_]/',$k)){ if ($keyupper && !preg_match('/[A-Z-_]/',$k)){
/* /*
@ -100,7 +101,7 @@ if (!function_exists('fnmatch')) {
continue; continue;
} }
$arr[$k] = strtok($delim); $arr[strtolower($k)] = strtok($delim);
} }
return $arr; return $arr;
@ -156,10 +157,11 @@ if (!function_exists('fnmatch')) {
// $arr['key1'] = 'value1'; $arr['key2'] = 'value2'; etc. // $arr['key1'] = 'value1'; $arr['key2'] = 'value2'; etc.
function utils_kimplode($arr, $delim='|') { function utils_kimplode($arr, $delim='|') {
$string = ""; $string = "";
foreach ($arr as $k => $val) { foreach ($arr as $k => $val) {
if ($val) if ($val)
$string .= $k . $delim . $val . $delim; $string .= strtoupper($k) . $delim . ($val) . $delim;
} }
return $string; return $string;
} }

View File

@ -73,8 +73,10 @@
$lang['admin']['entry']['write']['msgs'] = array( $lang['admin']['entry']['write']['msgs'] = array(
1 => 'Entry has been saved successfully', 1 => 'Entry has been saved successfully',
-1 => 'An error occurred while trying to save -1 => 'An error occurred: your entry could not be saved successfully',
the entry', -2 => 'An error occurred: your entry has not been saved; index might have become corrupt',
-3 => 'An error occurred: your entry has been saved as draft',
-4 => 'An error occurred: your entry has been saved as draft; index might have become corrupt',
'draft'=> 'You are editing a <strong>draft</strong> entry' 'draft'=> 'You are editing a <strong>draft</strong> entry'
); );

View File

@ -7,7 +7,7 @@
{action hook=admin_head} {action hook=admin_head}
</head> </head>
<body class="{"admin-$panel-$action"|tag:body_class:adminpanel}"> <body class="{"admin-$panel-$action"|tag:admin_body_class}">
<div id="body-container"> <div id="body-container">
<div id="outer-container"> <div id="outer-container">

View File

@ -554,7 +554,7 @@ a.link-general:hover, .main-cell a:hover {
cursor: help; cursor: help;
} }
.draft { background-color: lightgrey } .draft { background-color: #333 }
/* (already defined in common, here we put just some tuning settings) */ /* (already defined in common, here we put just some tuning settings) */
#admin-content ul.msgs { #admin-content ul.msgs {