position, $this->nodesize, $this->keylen); $this->open(); } } class entry_index { var $indices = array(); var $_offset = 0; var $_chunksize = 30; var $_keysize = 12; var $_lock_file = null; var $catlist = null; function __construct() { $this->_lock_file = CACHE_DIR . 'bpt.lock'; $this->catlist = entry_categories_list(); // only main index s a SBPlus (string BPlus): // the other (other categories) are managed // as if they were simple BPlus trees, so // values in key,value pairs won't // be strings but integers // // the integer will be the seek position // in the SBPlus' string file // // they'll be loaded back with the string file // as SBPlus trees: the string-key, string-value pair // will be returned if ($oldfile = file_exists($f = INDEX_DIR . 'index-0.dat')) $mode = 'r+b'; else $mode = 'w+b'; $this->indices [0] = new SBPlusTree(fopen($f, $mode), fopen(INDEX_DIR . 'index.strings.dat', $mode), 256, $this->_offset, $this->_chunksize, $this->_keysize); if ($oldfile) $this->indices [0]->open(); else $this->indices [0]->startup(); } function _lock_acquire($exclusive = true, $cat = 0) { if (file_exists($this->_lock_file)) { trigger_error("Could not acquire write lock on INDEX. " . "Didn't I told you FlatPress is not designed for concurrency, already? ;) " . "Don't worry: your entry has been saved as draft!", E_USER_WARNING); return false; } // simulates atomic write by writing to a file, then moving in place $tmp = $this->_lock_file . ".tmp"; if (io_write_file($tmp, 'dummy')) { if (rename($tmp, $this->_lock_file)) { return true; } } return false; } function _lock_release($cat = 0) { if (file_exists($this->_lock_file)) { return @unlink($this->_lock_file); } else { trigger_error("Lock file did not exist: ignoring (index was already unlocked.)", E_USER_NOTICE); return 2; } } function &get_index($cat = 0) { if (!is_numeric($cat)) trigger_error("CAT must be an integer ($cat was given)", E_USER_ERROR); if (!isset($this->indices [$cat])) { $f = INDEX_DIR . 'index-' . $cat . '.dat'; if ($oldfile = file_exists($f)) $mode = 'r+b'; else $mode = 'w+b'; $this->indices [$cat] = new BPlusTree(fopen($f, $mode), $this->_offset, $this->_chunksize, $this->_keysize); if ($oldfile) $this->indices [$cat]->open(); else $this->indices [$cat]->startup(); } return $this->indices [$cat]; } function add($id, $entry, $del = array(), $update_title = true) { $key = entry_idtokey($id); $val = $entry ['subject']; if (!$this->_lock_acquire()) return false; // we're DOOMED! $main = & $this->get_index(); $seek = null; // title must not be updated, let's get the offset value from has_key if (!$update_title) $seek = $main->has_key($key, $val); // if seek is null, then there is no such key, and we must set it // in the main index if (!is_numeric($seek)) $seek = $main->setitem($key, $val); // key has been set, let's set the other indices (if any), and link them // to the title string using $seek if (isset($entry ['categories']) && is_array($entry ['categories'])) { $categories = array(); foreach ($entry ['categories'] as $cat) { // skip non-numeric special categories (such as 'draft') if (!is_numeric($cat)) continue; $categories [] = $cat; // traverse the full cat tree (in linearized form) // to update categories which eventually aren't // explicitly listed while ($parent = $this->catlist [$cat]) { $categories [] = $parent; $cat = $parent; } } // delete any duplicate $categories = array_unique($categories); foreach ($categories as $cat) { $this_index = & $this->get_index($cat); $this_index->setitem($key, $seek); } } // if the set of indices changed, some might have to be deleted if ($del) { foreach ($del as $cat) { // echo 'DEL '. $cat,"\n"; if (!is_numeric($cat)) continue; $this_index = & $this->get_index($cat); $this_index->delitem($key); } } return $this->_lock_release(); } function delete($id, $entry) { $key = entry_idtokey($id); if (!$this->_lock_acquire()) return false; // we're DOOMED! $main = & $this->get_index(); $main->delitem($key); if (isset($entry ['categories']) && is_array($entry ['categories'])) { foreach ($entry ['categories'] as $cat) { if (!is_numeric($cat)) continue; $this_index = & $this->get_index($cat); if ($this_index->has_key($key)) $this_index->delitem($key); } } return $this->_lock_release(); } } class entry_archives extends fs_filelister { var $_directory = CONTENT_DIR; var $_y = null; var $_m = null; var $_d = null; var $_count = 0; var $_filter = 'entry*'; function __construct($y, $m = null, $d = null) { $this->_y = $y; $this->_m = $m; $this->_d = $d; $this->_directory .= "$y/"; if ($m) { $this->_directory .= "$m/"; if ($d) { $this->_filter = "entry$y$m$d*"; } } return parent::__construct(); } function _checkFile($directory, $file) { $f = "$directory/$file"; if (is_dir($f) && ctype_digit($file)) { return 1; } if (fnmatch($this->_filter . EXT, $file)) { $id = basename($file, EXT); $this->_count++; array_push($this->_list, $id); return 0; } } function getList() { rsort($this->_list); return parent::getList(); } function getCount() { return $this->_count; } } // //work in progress // class entry { // var $_indexer; // var $id; // function entry($id, $content) { // //$this->_indexer =& $indexer; // } // function get($field) { // $field = strtolower($field); // if (!isset($this->$field)) { // // if it is not set // // tries to fetch from the database // $arr = entry_parse($id); // while(list($field, $val) = each($arr)) // $this->$field = $val; // // if still is not set raises an error // if (!isset($this->$field)) // trigger_error("$field is not set", E_USER_NOTICE); // return; // } // return $this->$field; // } // function set($field, $val) { // $field = strtolower($field); // $this->$field = $val; // } // } /** * function entry_init * fills the global array containing the entry object */ function &entry_init() { // global $fpdb; // $fpdb->init(); static $entry_index = null; if (is_null($entry_index)) $entry_index = new entry_index(); return $entry_index; } function &entry_cached_index($id_cat) { $F = INDEX_DIR . 'index-' . $id_cat . '.dat'; if (!file_exists($F)) { $o = false; } else { $o = new entry_cached_index($id_cat); } return $o; } /* * function entry_query($params=array()){ * * global $fpdb; * $queryid = $fpdb->query($params); * $fpdb->doquery($queryid); * * * } * * function entry_hasmore() { * global $fpdb; * return $fpdb->hasmore(); * * } * * function entry_get() { * $fpdb->get(); * } */ function entry_keytoid($key) { $date = substr($key, 0, 6); $time = substr($key, 6); return "entry{$date}-{$time}"; } function entry_idtokey($id) { return substr($id, 5, 6) . substr($id, 12); } function entry_timetokey($time) { return date('ymdHis', $time); } function entry_keytotime($key) { $arr ['y'] = substr($key, 0, 2); $arr ['m'] = substr($key, 2, 2); $arr ['d'] = substr($key, 4, 2); $arr ['H'] = substr($key, 6, 2); $arr ['M'] = substr($key, 8, 2); $arr ['S'] = substr($key, 10, 2); return mktime($arr ['H'], $arr ['M'], $arr ['S'], $arr ['m'], $arr ['d'], $arr ['y']); } function entry_idtotime($id) { $date = date_from_id($id); return $date ['time']; } function entry_list() { trigger_error('function deprecated', E_USER_ERROR); $obj = & entry_init(); $entry_arr = $obj->getList(); if ($entry_arr) { krsort($entry_arr); return $entry_arr; } } function entry_exists($id) { $f = entry_dir($id) . EXT; return file_exists($f) ? $f : false; } function entry_dir($id, $month_only = false) { if (!preg_match('|^entry[0-9]{6}-[0-9]{6}$|', $id)) return false; $date = date_from_id($id); if ($month_only) $f = CONTENT_DIR . "{$date['y']}/{$date['m']}/"; else $f = CONTENT_DIR . "{$date['y']}/{$date['m']}/$id"; return $f; } function entry_parse($id, $raw = false) { $f = entry_exists($id); if (!$f) return array(); $fc = io_load_file($f); if (!$fc) return array(); $arr = utils_kexplode($fc); // propagates the error if entry does not exist if (isset($arr ['categories']) && // fix to bad old behaviour: (trim($arr ['categories']) != '')) { $cats = (array) explode(',', $arr ['categories']); $arr ['categories'] = (array) $cats; } else $arr ['categories'] = array(); // if (!is_array($arr['categories'])) die(); if (!isset($arr ['AUTHOR'])) { global $fp_config; $arr ['AUTHOR'] = $fp_config ['general'] ['author']; } if ($raw) return $arr; return $arr; } /** * function entry_get_comments * * @param * string id entry id * @param * array entry entry content array by ref; 'commentcount' field is added to the array * * @return object comment_indexer as reference * */ function &entry_get_comments($id, &$count) { $obj = new comment_indexer($id); $count = count($obj->getList()); return $obj; } function entry_categories_encode($cat_file) { // if ($string = io_load_file(CONTENT_DIR . 'categories.txt')) { $lines = explode("\n", trim($cat_file)); $idstack = $result = $indentstack = array(); while (!empty($lines)) { $v = array_pop($lines); $vt = trim($v); if ($vt) { $text = ''; $indent = utils_countdashes($vt, $text); $val = explode(':', $text); $id = trim($val [1]); $label = trim($val [0]); // IDs must be strictly positive if ($label && $id <= 0) return -1; if (empty($indentstack)) { array_push($indentstack, $indent); array_push($idstack, $id); $indent_old = $indent; } else { $indent_old = end($indentstack); } if ($indent < $indent_old) { array_push($indentstack, $indent); array_push($idstack, $id); } elseif ($indent > $indent_old) { $idstack = array( $id ); $indentstack = array( $indent ); } else { array_pop($idstack); $idstack = array( $id ); } $result ['rels'] [$id] = $idstack; $result ['defs'] [$id] = $label; } } ksort($result ['rels']); ksort($result ['defs']); // print_r($result); return io_write_file(CONTENT_DIR . 'categories_encoded.dat', serialize($result)); // } return false; } /* * * function entry_categories_print(&$lines, &$indentstack, &$result, $params) { * * * } * */ function entry_categories_list() { if (!$string = io_load_file(CONTENT_DIR . 'categories.txt')) return false; $lines = explode("\n", trim($string)); $idstack = array( 0 ); $indentstack = array(); // $categories = array(0=>null); $lastindent = 0; $lastid = 0; $parent = 0; $NEST = 0; foreach ($lines as $v) { $vt = trim($v); if (!$vt) continue; $text = ''; $indent = utils_countdashes($vt, $text); $val = explode(':', $text); $id = trim($val [1]); $label = trim($val [0]); // echo "PARSE: $id:$label\n"; if ($indent > $lastindent) { // echo "INDENT ($indent, $id, $lastid)\n"; $parent = $lastid; array_push($indentstack, $lastindent); array_push($idstack, $lastid); $lastindent = $indent; $NEST++; } elseif ($indent < $lastindent) { // echo "DEDENT ($indent)\n"; do { $dedent = array_pop($indentstack); array_pop($idstack); $NEST--; } while ($dedent > $indent); if ($dedent < $indent) return false; // trigger_error("failed parsing ($dedent<$indent)", E_USER_ERROR); $parent = end($idstack); $lastindent = $indent; $lastid = $id; } $lastid = $id; // echo "NEST: $NEST\n"; $categories [$id] = $parent; } return $categories; } function entry_categories_get($what = null) { global $fpdb; $categories = array(); if (!empty($fpdb->_categories)) { $categories = $fpdb->_categories; } else { $f = CONTENT_DIR . 'categories_encoded.dat'; if (file_exists($f)) { if ($c = io_load_file($f)) $categories = unserialize($c); } } if ($categories) { if ($what == 'defs' || $what == 'rels') return $categories [$what]; else return $categories; } return array(); } /** * flags are actually special categories * which are usually hidden. * * they can be set when editing your entries * to let flatpress perform special actions * * draft: Draft entry (hidden, awaiting publication) * static: Static entry (allows saving an alias, so you can reach it with * ?page=myentry) * commslock: Comments locked (comments disallowed for this entry) */ function entry_flags_get() { return array( 'draft', // 'static', 'commslock' ); } // @TODO : check against schema ? function entry_prepare(&$entry) { // prepare for serialization global $post; // fill in missing value if (!isset($entry ['date'])) { $entry ['date'] = date_time(); } // 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; } /** * * @param * array entry contents * @param * string|null entry id, null if can be deducted from the date field of $entry; * defaults to null * * @param * bool updates entry index; defaults to true * * * @return integer -1 failure while storing preliminar draft, abort. Index not touched. * -2 index updated succesfully, but draft doesn't exist anymore * (should never happen!) OR * failure while trying to move draft to entry path, draft does not exist anymore * index not touched * -3 error while moving draft still exists, index written succesfully but rolled back * -4 failure while saving to index, aborted (draft still exists) * * */ function entry_save($entry, $id = null, $update_index = true) { // PHASE 1 : prepare entry if (!$id) { if (!@$entry ['date']) $entry ['date'] = date_time(); $id = bdb_idfromtime(BDB_ENTRY, $entry ['date']); } // PHASE 2 : Store // secure data as DRAFT // (entry is also implicitly entry_prepare()'d here) $ret = draft_save($entry, $id); do_action('publish_post', $id, $entry); if ($ret === false) { return -1; // FAILURE: ABORT } // PHASE 3 : Update index $delete_cats = array(); $all_cats = @$entry ['categories']; $update_title = true; 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) { $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); fs_mkdir($entryd); $ret = rename($draftf, $entryf); if (!$ret) { if (draft_exists($id)) { // rollback changes in the index // (keep the draft file) if ($update_index) { $INDEX->delete($id, $all_cats); } return -3; } else { return -2; } } else { // SUCCESS : delete draft, move comments along draft_to_entry($id); return $id; } } return -4; } function entry_delete($id) { if (!$f = entry_exists($id)) return; /* * $d = bdb_idtofile($id,BDB_COMMENT); * fs_delete_recursive("$d"); * * // thanks to cimangi for noticing this * $f = dirname($d) . '/view_counter' .EXT; * fs_delete($f); * * * $f = bdb_idtofile($id); */ $d = entry_dir($id); fs_delete_recursive($d); $obj = & entry_init(); $obj->delete($id, entry_parse($id)); do_action('delete_post', $id); return fs_delete($f); } function entry_purge_cache() { $obj = & entry_init(); $obj->purge(); } // add_action('init', ?>