flatpress/fp-includes/core/core.entry.php
2023-10-28 13:56:05 +02:00

821 lines
17 KiB
PHP
Executable File

<?php
class entry_cached_index extends caching_SBPT {
// cache_filelister {
var $position = 0;
var $nodesize = 30;
var $keylen = 12;
/**
* opens the index belonging to a given category
*
* @params int $id_cat
*/
function __construct($id_cat = 0) {
$F = INDEX_DIR . 'index-' . $id_cat . '.dat';
if (!file_exists($F)) {
trigger_error("Can't find index '{$F}'", E_USER_ERROR);
}
parent::__construct(fopen($F, 'rb'), fopen(INDEX_DIR . 'index.strings.dat', 'rb'), 256, $this->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',
?>