bludit/bl-kernel/dbpages.class.php
Hakim Zulkufli e66834b5a6 A new function is created to achieve this, Text::truncate
which is based on Text::cut from the text.class.php helper.

Limit is set to 60 characters.

Example A:

Content has over 60 characters.
Title = First 60 characters + remaining characters of the last word + '...'
Example B:

Content has less than or equal to 60 characters
Title = All.
Example C:

Content only has one word but is over 60 characters.
Title = First 60 characters + '...'
Why B and C differ from each other?
Because we do not want the whole that one weird word
with over 60 characters to make its way to the title.
It's probably unrealistic, the code can be much cleaner
with B and C combined.

Feel free to discuss.
2017-08-06 22:17:34 +08:00

688 lines
16 KiB
PHP

<?php defined('BLUDIT') or die('Bludit CMS.');
class dbPages extends dbJSON
{
private $parentKeyList = array();
private $dbFields = array(
'title'=> array('inFile'=>true, 'value'=>''),
'content'=> array('inFile'=>true, 'value'=>''),
'description'=> array('inFile'=>false, 'value'=>''),
'username'=> array('inFile'=>false, 'value'=>''),
'tags'=> array('inFile'=>false, 'value'=>array()),
'status'=> array('inFile'=>false, 'value'=>'draft'), // published, draft, scheduled
'date'=> array('inFile'=>false, 'value'=>''),
'dateModified'=> array('inFile'=>false, 'value'=>''),
'position'=> array('inFile'=>false, 'value'=>0),
'coverImage'=> array('inFile'=>false, 'value'=>''),
'category'=> array('inFile'=>false, 'value'=>''),
'md5file'=> array('inFile'=>false, 'value'=>''),
'uuid'=> array('inFile'=>false, 'value'=>''),
'allowComments'=> array('inFile'=>false, 'value'=>true)
);
function __construct()
{
parent::__construct(DB_PAGES);
}
// Create a new page
public function add($args, $climode=false)
{
$dataForDb = array(); // This data will be saved in the database
$dataForFile = array(); // This data will be saved in the file
// Generate title if empty
if( empty($args['title']) ) {
$args['title'] = Text::truncate($args['content'], 60);
// Assign the new title to the slug as well.
$args['slug'] = $args['title'];
}
// Generate description if empty
if( empty($args['description']) ) {
$args['description'] = Text::truncate($args['content'], 100);
}
// Generate key
$key = $this->generateKey($args['slug'], $args['parent']);
// Generate UUID
$args['uuid'] = $this->generateUUID();
// Date
$currentDate = Date::current(DB_DATE_FORMAT);
// Validate date
if( !Valid::date($args['date'], DB_DATE_FORMAT) ) {
$args['date'] = $currentDate;
}
// Schedule page
if( ($args['date']>$currentDate) && ($args['status']=='published') ) {
$args['status'] = 'scheduled';
}
foreach($this->dbFields as $field=>$options) {
if( isset($args[$field]) ) {
if($field=='tags') {
$value = $this->generateTags($args['tags']);
}
else {
if( !$options['inFile'] ) {
// Sanitize if will be stored on database
$value = Sanitize::html($args[$field]);
}
else {
$value = $args[$field];
}
}
}
else {
// Default value for the field
$value = $options['value'];
}
// Where the data is stored
if ($options['inFile']) {
$dataForFile[$field] = $this->stylingFieldsForFile($field, $value);
} else {
// Set type
settype($value, gettype($options['value']));
// Save on database
$dataForDb[$field] = $value;
}
}
if ($climode===false) {
// Create the directory
if( Filesystem::mkdir(PATH_PAGES.$key, true) === false ) {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to create the directory '.PATH_PAGES.$key);
return false;
}
// Make the index.txt and save the file.
$data = implode(PHP_EOL, $dataForFile);
if( file_put_contents(PATH_PAGES.$key.DS.FILENAME, $data) === false ) {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to put the content in the file '.FILENAME);
return false;
}
}
// Checksum MD5
$dataForDb['md5file'] = md5_file(PATH_PAGES.$key.DS.FILENAME);
// Insert in database
$this->db[$key] = $dataForDb;
// Sort database
$this->sortBy();
// Save database
$this->save();
return $key;
}
public function edit($args, $climode=false)
{
$dataForDb = array();
$dataForFile = array();
$newKey = $this->generateKey($args['slug'], $args['parent'], false, $args['key']);
// If the page is draft then the created time is the current
if( $this->db[$args['key']]['status']=='draft' ) {
$args['date'] = Date::current(DB_DATE_FORMAT);
}
else {
$args['date'] = $this->db[$args['key']]['date'];
}
// Current UUID
$args['uuid'] = $this->db[$args['key']]['uuid'];
// Modified date
$args['dateModified'] = Date::current(DB_DATE_FORMAT);
foreach($this->dbFields as $field=>$options) {
if( isset($args[$field]) ) {
if($field=='tags') {
$value = $this->generateTags($args['tags']);
}
else {
if( !$options['inFile'] ) {
// Sanitize if will be stored on database
$value = Sanitize::html($args[$field]);
}
else {
// Default value for the field
$value = $args[$field];
}
}
}
else {
$value = $options['value'];
}
if ($options['inFile']) {
$dataForFile[$field] = $this->stylingFieldsForFile($field, $value);
} else {
// Set type
settype($value, gettype($options['value']));
// Save on database
$dataForDb[$field] = $value;
}
}
if( $climode===false ) {
// Move the directory from old key to new key.
if($newKey!==$args['key']) {
if( Filesystem::mv(PATH_PAGES.$args['key'], PATH_PAGES.$newKey) === false ) {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to move the directory to '.PATH_PAGES.$newKey);
return false;
}
}
// Make the index.txt and save the file.
$data = implode("\n", $dataForFile);
if( file_put_contents(PATH_PAGES.$newKey.DS.FILENAME, $data) === false ) {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to put the content in the file '.FILENAME);
return false;
}
}
// Remove the old key
unset( $this->db[$args['key']] );
// Checksum MD5
$dataForDb['md5file'] = md5_file(PATH_PAGES.$newKey.DS.FILENAME);
// Insert in database
$this->db[$newKey] = $dataForDb;
// Sort database
$this->sortBy();
// Save database
$this->save();
return $newKey;
}
public function delete($key)
{
// Page doesn't exist in database
if(!$this->exists($key)) {
Log::set(__METHOD__.LOG_SEP.'The page does not exist. Key: '.$key);
}
// Delete the index.txt file
if( Filesystem::rmfile(PATH_PAGES.$key.DS.FILENAME) === false ) {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to delete the file '.FILENAME);
}
// Delete the directory
if( Filesystem::rmdir(PATH_PAGES.$key) === false ) {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to delete the directory '.PATH_PAGES.$key);
}
// Remove from database
unset($this->db[$key]);
// Save the database.
if( $this->save() === false ) {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to save the database file.');
}
return true;
}
// Change a value of a page
public function setField($key, $field, $value)
{
if( $this->exists($key) ) {
settype($value, gettype($this->dbFields[$field]['value']));
$this->db[$key][$field] = $value;
return $this->save();
}
return false;
}
public function setStatus($key, $value)
{
return $this->setField($key, 'status', $value);
}
// Returns a database with published pages
public function getPublishedDB()
{
$tmp = $this->db;
foreach($tmp as $key=>$fields) {
if($fields['status']!='published') {
unset($tmp[$key]);
}
}
return $tmp;
}
// (array) Returns a database with the fixed pages
public function getFixedDB()
{
$tmp = $this->db;
foreach($tmp as $key=>$fields) {
if($fields['status']!='fixed') {
unset($tmp[$key]);
}
}
return $tmp;
}
// Returns a database with drafts pages
public function getDraftDB()
{
$tmp = $this->db;
foreach($tmp as $key=>$fields) {
if($fields['status']!='draft') {
unset($tmp[$key]);
}
}
return $tmp;
}
// Returns a database with drafts pages
public function getScheduledDB()
{
$tmp = $this->db;
foreach($tmp as $key=>$fields) {
if($fields['status']!='scheduled') {
unset($tmp[$key]);
}
}
return $tmp;
}
// Return an array with the database for a page, FALSE otherwise.
public function getPageDB($key)
{
if( $this->exists($key) ) {
return $this->db[$key];
}
return false;
}
// Returns an array with a list of pages, FALSE if out of range
// The database is sorted by date or by position
// (int) $pageNumber, the page number
// (int) $amountOfItems, amount of items to return, if -1 returns all the items
// (boolean) $onlyPublished, TRUE to return only published pages
public function getList($pageNumber, $amountOfItems, $onlyPublished=true)
{
$db = $this->db;
if( $onlyPublished ) {
$db = $this->getPublishedDB();
}
// Remove Error page from the list
unset($db['error']);
if ($amountOfItems==-1) {
return $db;
}
// The first page number is 1, so the real is 0
$realPageNumber = $pageNumber - 1;
$total = count($db);
$init = (int) $amountOfItems * $realPageNumber;
$end = (int) min( ($init + $amountOfItems - 1), $total );
$outrange = $init<0 ? true : $init>$end;
if(!$outrange) {
return array_slice($db, $init, $amountOfItems, true);
}
return false;
}
// Returns the amount of pages
// (boolean) $total, TRUE returns the total of pages
// (boolean) $total, FALSE returns the total of published pages (without draft and scheduled)
public function count($onlyPublished=true)
{
if( $onlyPublished ) {
$db = $this->getPublishedDB();
return count($db);
}
return count($this->db);
}
// Returns an array with all parents pages key, a parent page is not a child
public function getParents()
{
$db = $this->getPublishedDB();
foreach($db as $key=>$fields) {
// if the key has slash then is a child
if( Text::stringContains($key, '/') ) {
unset($db[$key]);
}
}
return $db;
}
// Return TRUE if the page exists, FALSE otherwise
public function exists($key)
{
return isset( $this->db[$key] );
}
public function sortBy()
{
if( ORDER_BY=='date' ) {
return $this->sortByDate(true);
} else {
return $this->sortByPosition(false);
}
}
// Sort pages by position
public function sortByPosition($HighToLow=false)
{
if($HighToLow) {
uasort($this->db, array($this, 'sortByPositionHighToLow'));
}
else {
uasort($this->db, array($this, 'sortByPositionLowToHigh'));
}
return true;
}
private function sortByPositionLowToHigh($a, $b) {
return $a['position']>$b['position'];
}
private function sortByPositionHighToLow($a, $b) {
return $a['position']<$b['position'];
}
// Sort pages by date
public function sortByDate($HighToLow=true)
{
if($HighToLow) {
uasort($this->db, array($this, 'sortByDateHighToLow'));
}
else {
uasort($this->db, array($this, 'sortByDateLowToHigh'));
}
return true;
}
private function sortByDateLowToHigh($a, $b) {
return $a['date']>$b['date'];
}
private function sortByDateHighToLow($a, $b) {
return $a['date']<$b['date'];
}
private function generateUUID() {
return md5( uniqid().time() );
}
// Returns TRUE if there are new pages published, FALSE otherwise
public function scheduler()
{
// Get current date
$currentDate = Date::current(DB_DATE_FORMAT);
$saveDatabase = false;
// The database need to be sorted by date
foreach($this->db as $pageKey=>$fields) {
if($fields['status']=='scheduled') {
if($fields['date']<=$currentDate) {
$this->db[$pageKey]['status'] = 'published';
$saveDatabase = true;
}
}
elseif( ($fields['status']=='published') && (ORDER_BY=='date') ) {
break;
}
}
if($saveDatabase) {
if( $this->save() === false ) {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to save the database file.');
return false;
}
Log::set(__METHOD__.LOG_SEP.'New pages published from the scheduler.');
return true;
}
return false;
}
// Generate a valid Key/Slug
public function generateKey($text, $parent=false, $returnSlug=false, $oldKey='')
{
if(Text::isEmpty($text)) {
$text = 'empty';
}
if( empty($parent) ) {
$newKey = Text::cleanUrl($text);
}
else {
$newKey = Text::cleanUrl($parent).'/'.Text::cleanUrl($text);
}
if($newKey!==$oldKey) {
// Verify if the key is already been used
if( isset($this->db[$newKey]) ) {
if( !Text::endsWithNumeric($newKey) ) {
$newKey = $newKey.'-0';
}
while( isset($this->db[$newKey]) ) {
$newKey++;
}
}
}
if($returnSlug) {
$explode = explode('/', $newKey);
if(isset($explode[1])) {
return $explode[1];
}
return $explode[0];
}
return $newKey;
}
public function rescanClimode()
{
Log::set('CLI MODE'.LOG_SEP.'Starting re-scan on pages directory.');
$pageList = array();
// Search for pages
$directories = Filesystem::listDirectories(PATH_PAGES, $regex='*', $sortByDate=false);
foreach($directories as $directory) {
if( Sanitize::pathFile($directory.DS.FILENAME) ) {
$pageKey = basename($directory);
$pageList[$pageKey] = true;
// Search for children pages
$subDirectories = Filesystem::listDirectories(PATH_PAGES.$pageKey.DS, $regex='*', $sortByDate=false);
foreach($subDirectories as $subDirectory) {
if( Sanitize::pathFile($subDirectory.DS.FILENAME) ) {
$subPageKey = basename($subDirectory);
$subPageKey = $pageKey.'/'.$subPageKey;
$pageList[$subPageKey] = true;
}
}
}
}
Log::set('CLI MODE'.LOG_SEP.'Updating pages...');
$keys = array_keys($pageList);
foreach($keys as $pageKey) {
// Checksum
$checksum = md5_file(PATH_PAGES.$pageKey.DS.FILENAME);
// New page
if( !isset($this->db[$pageKey]) ) {
$this->verifyFieldsClimode($pageKey, true);
}
// Update page
elseif($this->db[$pageKey]['md5file']!=$checksum) {
$this->verifyFieldsClimode($pageKey, false);
}
}
Log::set('CLI MODE'.LOG_SEP.'Removing pages...');
foreach( array_diff_key($this->db, $pageList) as $pageKey=>$data ) {
Log::set('CLI MODE'.LOG_SEP.'Removing page from database, key: '.$pageKey);
unset( $this->db[$pageKey] );
}
$this->save();
}
private function verifyFieldsClimode($key, $insert=true)
{
$page = new Page($key);
$db = $page->getDB();
// Content from file
$db['content'] = $db['contentRaw'];
// Parent
$db['parent'] = '';
$db['slug'] = $key;
$explodeKey = explode('/', $key);
if(isset($explodeKey[1])) {
$db['parent'] = $explodeKey[0];
$db['slug'] = $explodeKey[1];
}
// Date
if( !isset($db['date']) ) {
$db['date'] = Date::current(DB_DATE_FORMAT);
}
// Status
if( !isset($db['status']) ) {
$db['status'] = CLI_STATUS;
}
// Owner username
if( !isset($db['username']) ) {
$db['username'] = CLI_USERNAME;
}
// New page or update page
if($insert) {
Log::set('CLI MODE'.LOG_SEP.'New page found, key:'.$key);
return $this->add($db, $climode=true);
} else {
Log::set('CLI MODE'.LOG_SEP.'Different checksum, updating page, key:'.$key);
return $this->edit($db, $climode=true);
}
}
private function stylingFieldsForFile($field, $value)
{
// Support for Markdown files, good approach for Github
if (FILENAME==='index.md') {
if ($field==='title') {
return '#Title: '.$value;
} elseif ($field==='content') {
return '---'.PHP_EOL.$value;
} else {
return '<!-- '.Text::firstCharUp($field).': '.$value.' -->';
}
}
// Legacy style of Bludit with index.txt
if ($field==='content') {
return 'Content:'.PHP_EOL.$value;
}
return Text::firstCharUp($field).': '.$value;
}
public function getDBFields()
{
return $this->dbFields;
}
// ----- OLD
public function parentKeyList()
{
return $this->parentKeyList;
}
public function parentKeyExists($key)
{
return isset( $this->parentKeyList[$key] );
}
public function addParentKey($key)
{
$this->parentKeyList[$key] = $key;
}
// Returns the database
public function getDB()
{
return $this->db;
}
// Returns an Array, array('tagSlug'=>'tagName')
// (string) $tags, tag list separeted by comma.
public function generateTags($tags)
{
$tmp = array();
$tags = trim($tags);
if(empty($tags)) {
return $tmp;
}
// Make array
$tags = explode(',', $tags);
foreach($tags as $tag)
{
$tag = trim($tag);
$tagKey = Text::cleanUrl($tag);
$tmp[$tagKey] = $tag;
}
return $tmp;
}
// Change all posts with the old category key for the new category key
public function changeCategory($oldCategoryKey, $newCategoryKey)
{
foreach($this->db as $key=>$value) {
if($value['category']==$oldCategoryKey) {
$this->db[$key]['category'] = $newCategoryKey;
}
}
// Save database
return $this->save();
}
}