PageRenderTime 59ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/trunk/flatpress/fp-includes/core/core.entry.php

https://bitbucket.org/alexandrul/flatpress
PHP | 872 lines | 472 code | 212 blank | 188 comment | 88 complexity | 1a0d1c2d832efda6ca99be211686e2c6 MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0, MIT
  1. <?php
  2. class entry_cached_index extends caching_SBPT { #cache_filelister {
  3. var $position = 0;
  4. var $nodesize = 30;
  5. var $keylen = 12;
  6. /**
  7. * opens the index belonging to a given category
  8. * @params int $id_cat
  9. */
  10. function entry_cached_index($id_cat=0) {
  11. $F = INDEX_DIR.'index-'.$id_cat.'.dat';
  12. if (!file_exists($F)) {
  13. trigger_error ("Can't find index '{$F}'", E_USER_ERROR);
  14. }
  15. parent::caching_SBPT(
  16. fopen($F, 'rb'),
  17. fopen(INDEX_DIR.'index.strings.dat', 'rb'),
  18. 256,
  19. $this->position,
  20. $this->nodesize,
  21. $this->keylen
  22. );
  23. $this->open();
  24. }
  25. }
  26. class entry_index {
  27. var $indices = array();
  28. var $_offset = 0;
  29. var $_chunksize = 30;
  30. var $_keysize = 12;
  31. var $_lock_file = null;
  32. function entry_index() {
  33. $this->_lock_file = CACHE_DIR.'bpt.lock';
  34. $this->catlist = entry_categories_list();
  35. // only main index s a SBPlus (string BPlus):
  36. // the other (other categories) are managed
  37. // as if they were simple BPlus trees, so
  38. // values in key,value pairs won't
  39. // be strings but integers
  40. //
  41. // the integer will be the seek position
  42. // in the SBPlus' string file
  43. //
  44. // they'll be loaded back with the string file
  45. // as SBPlus trees: the string-key, string-value pair
  46. // will be returned
  47. if ($oldfile = file_exists($f=INDEX_DIR.'index-0.dat'))
  48. $mode = 'r+b';
  49. else
  50. $mode = 'w+b';
  51. $this->indices[0] = new SBPlusTree(
  52. fopen($f, $mode),
  53. fopen(INDEX_DIR.'index.strings.dat', $mode),
  54. 256,
  55. $this->_offset,
  56. $this->_chunksize,
  57. $this->_keysize
  58. );
  59. if ($oldfile)
  60. $this->indices[0]->open();
  61. else
  62. $this->indices[0]->startup();
  63. }
  64. function _lock_acquire($exclusive=true, $cat=0) {
  65. if (file_exists($this->_lock_file)) {
  66. trigger_error("Could not acquire write lock on INDEX. ".
  67. "Didn't I told you FlatPress is not designed for concurrency, already? ;) ".
  68. "Don't worry: your entry has been saved as draft!", E_USER_WARNING);
  69. return false;
  70. }
  71. // simulates atomic write by writing to a file, then moving in place
  72. $tmp = $this->_lock_file.".tmp";
  73. if (io_write_file($tmp, 'dummy')) {
  74. if (rename($tmp, $this->_lock_file)) {
  75. return true;
  76. }
  77. }
  78. return false;
  79. }
  80. function _lock_release($cat=0) {
  81. if (file_exists($this->_lock_file)) {
  82. return @unlink($this->_lock_file);
  83. } else {
  84. trigger_error("Lock file did not exist: ignoring (index was already unlocked.)", E_USER_NOTICE);
  85. return 2;
  86. }
  87. }
  88. function &get_index($cat=0) {
  89. if (!is_numeric($cat))
  90. trigger_error("CAT must be an integer ($cat was given)", E_USER_ERROR);
  91. if (!isset($this->indices[$cat])) {
  92. $f = INDEX_DIR.'index-'.$cat.'.dat';
  93. if ($oldfile = file_exists($f))
  94. $mode = 'r+b';
  95. else $mode = 'w+b';
  96. $this->indices[$cat] = new BPlusTree(
  97. fopen($f, $mode),
  98. $this->_offset,
  99. $this->_chunksize,
  100. $this->_keysize
  101. );
  102. if ($oldfile)
  103. $this->indices[$cat]->open();
  104. else $this->indices[$cat]->startup();
  105. }
  106. return $this->indices[$cat];
  107. }
  108. function add($id, $entry, $del = array(), $update_title = true) {
  109. $key = entry_idtokey($id);
  110. $val = $entry['subject'];
  111. if (!$this->_lock_acquire()) return false; // we're DOOMED!
  112. $main =& $this->get_index();
  113. $seek = null;
  114. // title must not be updated, let's get the offset value from has_key
  115. if (!$update_title)
  116. $seek = $main->has_key($key, $val);
  117. // if seek is null, then there is no such key, and we must set it
  118. // in the main index
  119. if (!is_numeric($seek))
  120. $seek = $main->setitem($key, $val);
  121. // key has been set, let's set the other indices (if any), and link them
  122. // to the title string using $seek
  123. if (isset($entry['categories']) && is_array($entry['categories'])) {
  124. $categories = array();
  125. foreach ($entry['categories'] as $cat) {
  126. // skip non-numeric special categories (such as 'draft')
  127. if (!is_numeric($cat)) continue;
  128. $categories[] = $cat;
  129. // traverse the full cat tree (in linearized form)
  130. // to update categories which eventually aren't
  131. // explicitly listed
  132. while ($parent = $this->catlist[ $cat ]) {
  133. $categories[] = $parent;
  134. $cat = $parent;
  135. }
  136. }
  137. // delete any duplicate
  138. $categories = array_unique($categories);
  139. foreach ($categories as $cat) {
  140. $this_index =& $this->get_index($cat);
  141. $this_index->setitem($key, $seek);
  142. }
  143. }
  144. // if the set of indices changed, some might have to be deleted
  145. if ($del) {
  146. foreach($del as $cat) {
  147. // echo 'DEL '. $cat,"\n";
  148. if (!is_numeric($cat)) continue;
  149. $this_index =& $this->get_index($cat);
  150. $this_index->delitem($key);
  151. }
  152. }
  153. return $this->_lock_release();
  154. }
  155. function delete($id, $entry) {
  156. $key = entry_idtokey($id);
  157. if (!$this->_lock_acquire()) return false; // we're DOOMED!
  158. $main =& $this->get_index();
  159. $main->delitem($key);
  160. if (isset($entry['categories']) && is_array($entry['categories'])) {
  161. foreach ($entry['categories'] as $cat) {
  162. if (!is_numeric($cat)) continue;
  163. $this_index =& $this->get_index($cat);
  164. if ($this_index->has_key($key))
  165. $this_index->delitem($key);
  166. }
  167. }
  168. return $this->_lock_release();
  169. }
  170. }
  171. class entry_archives extends fs_filelister {
  172. var $_directory = CONTENT_DIR;
  173. var $_y = null;
  174. var $_m = null;
  175. var $_d = null;
  176. var $_count = 0;
  177. var $_filter = 'entry*';
  178. function entry_archives($y, $m = null, $d = null) {
  179. $this->_y = $y;
  180. $this->_m = $m;
  181. $this->_d = $d;
  182. $this->_directory .= "$y/";
  183. if ($m){
  184. $this->_directory .= "$m/";
  185. if ($d) {
  186. $this->_filter = "entry$y$m$d*";
  187. }
  188. }
  189. return parent::fs_filelister();
  190. }
  191. function _checkFile($directory, $file) {
  192. $f = "$directory/$file";
  193. if ( is_dir($f) && ctype_digit($file)) {
  194. return 1;
  195. }
  196. if (fnmatch($this->_filter.EXT, $file)) {
  197. $id=basename($file,EXT);
  198. $this->_count++;
  199. array_push($this->_list, $id);
  200. return 0;
  201. }
  202. }
  203. function getList() {
  204. rsort($this->_list);
  205. return parent::getList();
  206. }
  207. function getCount() {
  208. return $this->_count;
  209. }
  210. }
  211. /* //work in progress
  212. class entry {
  213. var $_indexer;
  214. var $id;
  215. function entry($id, $content) {
  216. //$this->_indexer =& $indexer;
  217. }
  218. function get($field) {
  219. $field = strtolower($field);
  220. if (!isset($this->$field)) {
  221. // if it is not set
  222. // tries to fetch from the database
  223. $arr = entry_parse($id);
  224. while(list($field, $val) = each($arr))
  225. $this->$field = $val;
  226. // if still is not set raises an error
  227. if (!isset($this->$field))
  228. trigger_error("$field is not set", E_USER_NOTICE);
  229. return;
  230. }
  231. return $this->$field;
  232. }
  233. function set($field, $val) {
  234. $field = strtolower($field);
  235. $this->$field = $val;
  236. }
  237. }
  238. */
  239. /**
  240. * function entry_init
  241. * fills the global array containing the entry object
  242. */
  243. function &entry_init() {
  244. #global $fpdb;
  245. #$fpdb->init();
  246. static $entry_index = null;
  247. if (is_null($entry_index))
  248. $entry_index= new entry_index;
  249. return $entry_index;
  250. }
  251. function &entry_cached_index($id_cat) {
  252. $F = INDEX_DIR.'index-'.$id_cat.'.dat';
  253. if (!file_exists($F)) {
  254. $o = false;
  255. } else {
  256. $o = new entry_cached_index($id_cat);
  257. }
  258. return $o;
  259. }
  260. /*
  261. function entry_query($params=array()){
  262. global $fpdb;
  263. $queryid = $fpdb->query($params);
  264. $fpdb->doquery($queryid);
  265. }
  266. function entry_hasmore() {
  267. global $fpdb;
  268. return $fpdb->hasmore();
  269. }
  270. function entry_get() {
  271. $fpdb->get();
  272. }
  273. */
  274. function entry_keytoid($key) {
  275. $date = substr($key,0,6);
  276. $time = substr($key,6);
  277. return "entry{$date}-{$time}";
  278. }
  279. function entry_idtokey($id) {
  280. return substr($id, 5, 6) . substr($id, 12);
  281. }
  282. function entry_timetokey($time) {
  283. return date('ymdHis', $time);
  284. }
  285. function entry_keytotime($key) {
  286. $arr[ 'y' ] = substr($key, 0, 2);
  287. $arr[ 'm' ] = substr($key, 2, 2);
  288. $arr[ 'd' ] = substr($key, 4, 2);
  289. $arr[ 'H' ] = substr($key, 6, 2);
  290. $arr[ 'M' ] = substr($key, 8, 2);
  291. $arr[ 'S' ] = substr($key, 10, 2);
  292. return mktime($arr['H'], $arr['M'], $arr['S'],
  293. $arr['m'], $arr['d'], $arr['y']);
  294. }
  295. function entry_idtotime($id) {
  296. $date = date_from_id($id);
  297. return $date['time'];
  298. }
  299. function entry_list() {
  300. trigger_error('function deprecated', E_USER_ERROR);
  301. $obj =& entry_init();
  302. $entry_arr = $obj->getList();
  303. if ($entry_arr) {
  304. krsort($entry_arr);
  305. return $entry_arr;
  306. }
  307. }
  308. function entry_exists($id) {
  309. $f = entry_dir($id).EXT;
  310. return file_exists($f)? $f : false;
  311. }
  312. function entry_dir($id, $month_only = false) {
  313. if (!preg_match('|^entry[0-9]{6}-[0-9]{6}$|', $id))
  314. return false;
  315. $date = date_from_id($id);
  316. if ($month_only)
  317. $f = CONTENT_DIR . "{$date['y']}/{$date['m']}/";
  318. else
  319. $f = CONTENT_DIR . "{$date['y']}/{$date['m']}/$id";
  320. return $f;
  321. }
  322. function entry_parse($id, $raw=false) {
  323. $f = entry_exists($id);
  324. if (!$f)
  325. return array();
  326. $fc = io_load_file($f);
  327. if (!$fc)
  328. return array();
  329. $arr = utils_kexplode($fc);
  330. // propagates the error if entry does not exist
  331. if (isset($arr['categories']) && // fix to bad old behaviour:
  332. (trim($arr['categories']) != '')) {
  333. $cats = (array)explode(',',$arr['categories']);
  334. $arr['categories'] = (array) $cats;
  335. } else $arr['categories'] = array();
  336. // if (!is_array($arr['categories'])) die();
  337. if (!isset($arr['AUTHOR'])) {
  338. global $fp_config;
  339. $arr['AUTHOR'] = $fp_config['general']['author'];
  340. }
  341. if ($raw) return $arr;
  342. return $arr;
  343. }
  344. /**
  345. * function entry_get_comments
  346. *
  347. * @param string id entry id
  348. * @param array entry entry content array by ref; 'commentcount' field is added to the array
  349. *
  350. * @return object comment_indexer as reference
  351. *
  352. */
  353. function &entry_get_comments($id, &$count) {
  354. $obj = new comment_indexer($id);
  355. $count = count($obj->getList());
  356. return $obj;
  357. }
  358. function entry_categories_encode($cat_file) {
  359. //if ($string = io_load_file(CONTENT_DIR . 'categories.txt')) {
  360. $lines = explode("\n", trim($cat_file));
  361. $idstack = $result = $indentstack=array();
  362. while (!empty($lines)) {
  363. $v = array_pop($lines);
  364. $vt = trim($v);
  365. if ($vt) {
  366. $text='';
  367. $indent = utils_countdashes($vt, $text);
  368. $val = explode(':', $text);
  369. $id = trim($val[1]);
  370. $label = trim($val[0]);
  371. // IDs must be strictly positive
  372. if ($label && $id <= 0) return -1;
  373. if (empty($indentstack)) {
  374. array_push($indentstack,$indent);
  375. array_push($idstack, $id);
  376. $indent_old = $indent;
  377. } else {
  378. $indent_old = end($indentstack);
  379. }
  380. if ($indent < $indent_old) {
  381. array_push($indentstack, $indent);
  382. array_push($idstack, $id);
  383. } elseif ($indent > $indent_old) {
  384. $idstack = array($id);
  385. $indentstack = array($indent);
  386. } else {
  387. array_pop($idstack);
  388. $idstack = array($id);
  389. }
  390. $result['rels'][$id] = $idstack;
  391. $result['defs'][$id] = $label;
  392. }
  393. }
  394. ksort($result['rels']);
  395. ksort($result['defs']);
  396. //print_r($result);
  397. return io_write_file(CONTENT_DIR . 'categories_encoded.dat', serialize($result));
  398. //}
  399. return false;
  400. }
  401. /*
  402. function entry_categories_print(&$lines, &$indentstack, &$result, $params) {
  403. }
  404. */
  405. function entry_categories_list() {
  406. if (!$string = io_load_file(CONTENT_DIR . 'categories.txt'))
  407. return false;
  408. $lines = explode("\n", trim($string));
  409. $idstack = array(0);
  410. $indentstack=array();
  411. // $categories = array(0=>null);
  412. $lastindent = 0;
  413. $lastid = 0;
  414. $parent = 0;
  415. $NEST = 0;
  416. foreach ($lines as $v) {
  417. $vt = trim($v);
  418. if (!$vt) continue;
  419. $text='';
  420. $indent = utils_countdashes($vt, $text);
  421. $val = explode(':', $text);
  422. $id = trim($val[1]);
  423. $label = trim($val[0]);
  424. // echo "PARSE: $id:$label\n";
  425. if ($indent > $lastindent) {
  426. // echo "INDENT ($indent, $id, $lastid)\n";
  427. $parent = $lastid;
  428. array_push($indentstack, $lastindent);
  429. array_push($idstack, $lastid);
  430. $lastindent = $indent;
  431. $NEST++;
  432. } elseif ($indent < $lastindent) {
  433. // echo "DEDENT ($indent)\n";
  434. do {
  435. $dedent = array_pop($indentstack);
  436. array_pop($idstack);
  437. $NEST--;
  438. } while ($dedent > $indent);
  439. if ($dedent < $indent) return false; //trigger_error("failed parsing ($dedent<$indent)", E_USER_ERROR);
  440. $parent = end($idstack);
  441. $lastindent = $indent;
  442. $lastid = $id;
  443. }
  444. $lastid = $id;
  445. // echo "NEST: $NEST\n";
  446. $categories[ $id ] = $parent;
  447. }
  448. return $categories;
  449. }
  450. function entry_categories_get($what=null) {
  451. global $fpdb;
  452. $categories = array();
  453. if (!empty($fpdb->_categories)) {
  454. $categories = $fpdb->_categories;
  455. } else {
  456. $f = CONTENT_DIR . 'categories_encoded.dat';
  457. if (file_exists($f)) {
  458. if ($c = io_load_file($f))
  459. $categories = unserialize($c);
  460. }
  461. }
  462. if ($categories) {
  463. if ($what=='defs' || $what=='rels')
  464. return $categories[$what];
  465. else
  466. return $categories;
  467. }
  468. return array();
  469. }
  470. /**
  471. flags are actually special categories
  472. which are usually hidden.
  473. they can be set when editing your entries
  474. to let flatpress perform special actions
  475. draft: Draft entry (hidden, awaiting publication)
  476. static: Static entry (allows saving an alias, so you can reach it with
  477. ?page=myentry)
  478. commslock: Comments locked (comments disallowed for this entry)
  479. */
  480. function entry_flags_get() {
  481. return array(
  482. 'draft',
  483. //'static',
  484. 'commslock'
  485. );
  486. }
  487. // @TODO : check against schema ?
  488. function entry_prepare(&$entry) { // prepare for serialization
  489. global $post;
  490. // fill in missing value
  491. if (!isset($entry['date'])) {
  492. $entry['date']=date_time();
  493. }
  494. // import into global scope
  495. $post = $entry;
  496. // apply *_pre filters
  497. $entry['content'] = apply_filters('content_save_pre', $entry['content']);
  498. $entry['subject'] = apply_filters('title_save_pre', $entry['subject']);
  499. // prepare for serialization
  500. if (isset($entry['categories'])) {
  501. if (!is_array($entry['categories'])) {
  502. trigger_error("Expected 'categories' to be an array, found "
  503. . gettype($entry['categories']), E_USER_WARNING);
  504. $entry['categories'] = array();
  505. }
  506. } else { $entry['categories'] = array(); }
  507. return $entry;
  508. }
  509. /**
  510. *
  511. * @param array entry contents
  512. * @param string|null entry id, null if can be deducted from the date field of $entry;
  513. * defaults to null
  514. *
  515. * @param bool updates entry index; defaults to true
  516. *
  517. *
  518. * @return integer -1 failure while storing preliminar draft, abort. Index not touched.
  519. * -2 index updated succesfully, but draft doesn't exist anymore
  520. * (should never happen!) OR
  521. * failure while trying to move draft to entry path, draft does not exist anymore
  522. * index not touched
  523. * -3 error while moving draft still exists, index written succesfully but rolled back
  524. * -4 failure while saving to index, aborted (draft still exists)
  525. *
  526. *
  527. */
  528. function entry_save($entry, $id=null, $update_index = true) {
  529. // PHASE 1 : prepare entry
  530. if (!$id) {
  531. if (!@$entry['date']) $entry['date'] = date_time();
  532. $id = bdb_idfromtime(BDB_ENTRY, $entry['date']);
  533. }
  534. // PHASE 2 : Store
  535. // secure data as DRAFT
  536. // (entry is also implicitly entry_prepare()'d here)
  537. $ret = draft_save($entry, $id);
  538. do_action('publish_post', $id, $entry);
  539. if ($ret === false) {
  540. return -1; // FAILURE: ABORT
  541. }
  542. // PHASE 3 : Update index
  543. $delete_cats = array();
  544. $all_cats = @$entry['categories'];
  545. $update_title = true;
  546. if ($old_entry = entry_parse($id)) {
  547. if ($all_cats) {
  548. $delete_cats = array_diff($old_entry['categories'], $all_cats);
  549. }
  550. $all_cats = $all_cats? array_merge($all_cats, $old_entry['categories']) : $old_entry['categories'];
  551. $update_title = $entry['subject'] != $old_entry['subject'];
  552. }
  553. /*
  554. echo 'old';
  555. print_r($old_entry['categories']);
  556. echo 'new';
  557. print_r($entry['categories']);
  558. echo 'del';
  559. print_r($delete_cats);
  560. echo 'all';
  561. print_r($all_cats);
  562. */
  563. $INDEX =& entry_init();
  564. $ok = ($update_index) ? $INDEX->add($id, $entry, $delete_cats, $update_title) : true;
  565. // PHASE 4 : index updated; let's move back the entry
  566. if ($ok) {
  567. $entryd = entry_dir($id, true);
  568. $entryf = $entryd.$id.EXT;
  569. $draftf = draft_exists($id);
  570. if ($draftf === false) { // this should never happen!
  571. if ($update_index) {
  572. $INDEX->delete($id, $all_cats);
  573. }
  574. return -2;
  575. }
  576. fs_delete($entryf);
  577. fs_mkdir($entryd);
  578. $ret = rename($draftf, $entryf);
  579. if (!$ret) {
  580. if (draft_exists($id)) {
  581. // rollback changes in the index
  582. // (keep the draft file)
  583. if ($update_index) {
  584. $INDEX->delete($id, $all_cats);
  585. }
  586. return -3;
  587. } else {
  588. return -2;
  589. }
  590. } else {
  591. // SUCCESS : delete draft, move comments along
  592. draft_to_entry($id);
  593. return $id;
  594. }
  595. }
  596. return -4;
  597. }
  598. function entry_delete($id) {
  599. if ( ! $f = entry_exists($id) )
  600. return;
  601. /*
  602. $d = bdb_idtofile($id,BDB_COMMENT);
  603. fs_delete_recursive("$d");
  604. // thanks to cimangi for noticing this
  605. $f = dirname($d) . '/view_counter' .EXT;
  606. fs_delete($f);
  607. $f = bdb_idtofile($id);
  608. */
  609. $d = entry_dir($id);
  610. fs_delete_recursive($d);
  611. $obj =& entry_init();
  612. $obj->delete($id, entry_parse($id));
  613. do_action('delete_post', $id);
  614. return fs_delete($f);
  615. }
  616. function entry_purge_cache() {
  617. $obj =& entry_init();
  618. $obj->purge();
  619. }
  620. //add_action('init',
  621. ?>