Source for file BitArticle.php
Documentation is available at BitArticle.php
* Article class is used when accessing BitArticles. It is based on TikiSample
* and builds on core bitweaver functionality, such as the Liberty CMS engine.
* @copyright 2004-15 bitweaver.org
* Copyright( c )2003 tikwiki.org
* Copyright( c )2002-2003, Luis Argerich, Garland Foster, Eduardo Polidor, et. al.
* All Rights Reserved. See below for details and a complete list of authors.
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See http://www.gnu.org/copyleft/lesser.html for details
* @author wolffy <wolff_borg@yahoo.com.au>
require_once( ARTICLES_PKG_PATH. 'BitArticleTopic.php' );
require_once( ARTICLES_PKG_PATH. 'BitArticleType.php' );
require_once( LIBERTY_PKG_PATH. 'LibertyMime.php' );
require_once( LIBERTY_PKG_PATH. 'LibertyComment.php' );
define( 'BITARTICLE_CONTENT_TYPE_GUID', 'bitarticle' );
* Primary key for articles
* Initiate the articles class
* @param $pArticleId article id of the article we want to view
* @param $pContentId content id of the article we want to view
public function BitArticle($pArticleId= NULL, $pContentId= NULL)
'content_name' => 'Article',
'handler_class' => 'BitArticle',
'handler_package' => 'articles',
'handler_file' => 'BitArticle.php',
'maintainer_url' => 'http://www.bitweaver.org'
$offset = $this->mDate->get_display_offset();
* Load the data from the database
public function load($pContentId = NULL, $pPluginParams = NULL)
// LibertyContent::load()assumes you have joined already, and will not execute any sql!
// This is a significant performance optimization
$this->getServicesSql( 'content_load_sql_function', $selectSql, $joinSql, $whereSql, $bindVars );
$query = "SELECT a.*, lc.*, atype.*, atopic.*, lch.hits,
uue.`login` AS `modifier_user`, uue.`real_name` AS `modifier_real_name`,
uuc.`login` AS `creator_user`, uuc.`real_name` AS `creator_real_name` ,
la.`attachment_id` AS `primary_attachment_id`, lf.`file_name` AS `image_attachment_path` $selectSql
LEFT OUTER JOIN `". BIT_DB_PREFIX. "article_types` atype ON( atype.`article_type_id` = a.`article_type_id` )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "article_topics` atopic ON( atopic.`topic_id` = a.`topic_id` )
INNER JOIN `". BIT_DB_PREFIX. "liberty_content` lc ON( lc.`content_id` = a.`content_id` )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "liberty_content_hits` lch ON( lch.`content_id` = lc.`content_id` )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "users_users` uue ON( uue.`user_id` = lc.`modifier_user_id` )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "users_users` uuc ON( uuc.`user_id` = lc.`user_id` )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "liberty_attachments` la ON( la.`content_id` = lc.`content_id` AND la.`is_primary` = 'y' )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "liberty_files` lf ON( lf.`file_id` = la.`foreign_id` )
WHERE a.`$lookupColumn`=? $whereSql";
$result = $this->mDb->query( $query, $bindVars );
if ( $result && $result->numRows() ) {
$this->mInfo = $result->fetchRow();
// if a custom image for the article exists, use that, then use an attachment, then use the topic image
$this->mInfo['thumbnail_url'] = static::getImageThumbnails( $this->mInfo );
$this->mInfo['creator'] = ( !empty( $this->mInfo['creator_real_name'] ) ? $this->mInfo['creator_real_name'] : $this->mInfo['creator_user'] );
$this->mInfo['editor'] = ( !empty( $this->mInfo['modifier_real_name'] ) ? $this->mInfo['modifier_real_name'] : $this->mInfo['modifier_user'] );
// we need the raw data to display in the textarea
// here we have the displayed data without the ...split... stuff
$this->mInfo['num_comments'] = $comment->getNumComments( $this->mInfo['content_id'] );
if ( !empty( $this->mInfo['primary_attachment_id'] ) && !empty( $this->mStorage[$this->mInfo['primary_attachment_id']] )) {
$this->mInfo['primary_attachment'] = &$this->mStorage[$this->mInfo['primary_attachment_id']];
* Store article data after submission
* @param array pParamHash of values that will be used to store the page
* @return bool TRUE on success, FALSE if store could not occur. If FALSE, $this->mErrors will have reason why
public function store(&$pParamHash)
$this->mDb->StartTrans();
$result = $this->mDb->associateUpdate( $table, $pParamHash['article_store'], array( "article_id" => $this->mArticleId ));
$pParamHash['article_store']['content_id'] = $pParamHash['content_id'];
if ( isset ( $pParamHash['article_id'] )&& is_numeric( $pParamHash['article_id'] ) ) {
// if pParamHash['article_id'] is set, someone is requesting a particular article_id. Use with caution!
$pParamHash['article_store']['article_id'] = $pParamHash['article_id'];
$pParamHash['article_store']['article_id'] = $this->mDb->GenID( 'articles_article_id_seq' );
$this->mArticleId = $pParamHash['article_store']['article_id'];
$result = $this->mDb->associateInsert( $table, $pParamHash['article_store'] );
$this->mDb->CompleteTrans();
* Make sure the data is safe to store
* @param pParamHash be sure to pass by reference in case we need to make modifcations to the hash
* @param array pParams reference to hash of values that will be used to store the page, they will be modified where necessary
* @return bool TRUE on success, FALSE if verify failed. If FALSE, $this->mErrors will have reason why
public function verify(&$pParamHash)
global $gBitUser, $gBitSystem;
// make sure we're all loaded up of we have a mArticleId
$pParamHash['content_id'] = $this->mInfo['content_id'];
// It is possible a derived class set this to something different
if ( empty( $pParamHash['content_type_guid'] )&& !empty( $this->mContentTypeGuid ) ) {
if ( @$this->verifyId( $pParamHash['content_id'] ) ) {
$pParamHash['article_store']['content_id'] = $pParamHash['content_id'];
if ( !empty( $pParamHash['author_name'] ) ) {
$pParamHash['article_store']['author_name'] = $pParamHash['author_name'];
if ( @$this->verifyId( $pParamHash['topic_id'] ) ) {
$pParamHash['article_store']['topic_id'] =(int) $pParamHash['topic_id'];
if ( @$this->verifyId( $pParamHash['article_type_id'] ) ) {
$pParamHash['article_store']['article_type_id'] =(int) $pParamHash['article_type_id'];
if ( !empty( $pParamHash['format_guid'] ) ) {
$pParamHash['content_store']['format_guid'] = $pParamHash['format_guid'];
// we do the substr on load. otherwise we need to store the same data twice.
if ( !empty( $pParamHash['edit'] ) ) {
$pParamHash['content_store']['data'] = $pParamHash['edit'];
if ( !empty( $pParamHash['rating'] ) ) {
$pParamHash['article_store']['rating'] =(int) ( $pParamHash['rating'] );
// check for name issues, first truncate length if too long
if ( !empty( $pParamHash['title'] ) ) {
if ( empty( $pParamHash['title'] ) ) {
$this->mErrors['title'] = 'You must specify a title.';
} elseif ( empty( $pParamHash['title'] ) ) {
$this->mErrors['title'] = 'You must specify a title';
if ( !empty( $pParamHash['publish_Month'] ) ) {
$dateString = $this->mDate->gmmktime(
$pParamHash['publish_Hour'],
$pParamHash['publish_Minute'],
isset ($pParamHash['publish_Second']) ? $pParamHash['publish_Second'] : 0,
$pParamHash['publish_Month'],
$pParamHash['publish_Day'],
$pParamHash['publish_Year']
$timestamp = $this->mDate->getUTCFromDisplayDate( $dateString );
$pParamHash['publish_date'] = $timestamp;
if ( !empty( $pParamHash['publish_date'] ) ) {
$pParamHash['article_store']['publish_date'] = $pParamHash['publish_date'];
if ( !empty( $pParamHash['expire_Month'] ) ) {
$dateString = $this->mDate->gmmktime(
$pParamHash['expire_Hour'],
$pParamHash['expire_Minute'],
isset ($pParamHash['expire_Second']) ? $pParamHash['expire_Second'] : 0,
$pParamHash['expire_Month'],
$pParamHash['expire_Day'],
$pParamHash['expire_Year']
$timestamp = $this->mDate->getUTCFromDisplayDate( $dateString );
$pParamHash['expire_date'] = $timestamp;
if ( !empty( $pParamHash['expire_date'] ) ) {
$pParamHash['article_store']['expire_date'] = $pParamHash['expire_date'];
if ( @$this->verifyId( $pParamHash['status_id'] ) ) {
if ($pParamHash['status_id'] > ARTICLE_STATUS_PENDING) {
if ( $gBitUser->hasPermission( 'p_articles_approve_submission' )) {
$pParamHash['article_store']['status_id'] =(int) ( $pParamHash['status_id'] );
$pParamHash['article_store']['status_id'] = ARTICLE_STATUS_PENDING;
$pParamHash['article_store']['status_id'] =(int) ( $pParamHash['status_id'] );
$pParamHash['article_store']['status_id'] = $this->mInfo['status_id'];
if ( $gBitUser->hasPermission( 'p_articles_approve_submission' ) || $gBitUser->hasPermission( 'p_articles_auto_approve' ) ) {
$pParamHash['article_store']['status_id'] = ARTICLE_STATUS_APPROVED;
$pParamHash['article_store']['status_id'] = ARTICLE_STATUS_PENDING; // Default status
if ( $gBitUser->hasPermission( 'p_liberty_enter_html' ) ) {
$prefs[] = 'content_enter_html';
foreach ($prefs as $pref) {
if ( !empty( $pParamHash['preferences'][$pref] ) ) {
$pParamHash['preferences_store'][$pref] = $pParamHash['preferences'][$pref];
$pParamHash['preferences_store'][$pref] = NULL;
if ( array_search( $pParamHash['article_store']['status_id'], array( ARTICLE_STATUS_DENIED, ARTICLE_STATUS_DRAFT, ARTICLE_STATUS_PENDING ) ) ) {
$this->mInfo["no_index"] = true ;
// if we have an error we get them all by checking parent classes for additional errors
parent::verify( $pParamHash );
* Deal with images and text, modify them apprpriately that they can be returned to the form.
* @param $previewData data submitted by form - generally $_REQUEST
* @return array of data compatible with article form
global $gBitSystem, $gBitUser;
$data = array_merge( $pParamHash, $data['content_store'], $data['article_store'] );
$data['raw'] = $data['edit'];
if ( empty( $data['user_id'] ) ) {
$data['user_id'] = $gBitUser->mUserId;
if ( empty( $data['hits'] ) ) {
if ( empty( $data['publish_date'] ) ) {
$data['publish_date'] = $gBitSystem->getUTCTime();
if ( empty( $data['article_type_id'] ) ) {
$data['article_type_id'] = 1;
if ( empty( $data['topic_id'] ) ) {
if ( empty( $data['parsed'] ) ) {
$data['no_cache'] = TRUE;
// replace the split syntax with a horizontal rule
$data = array_merge( $data, $articleType->mInfo, $articleTopic->mInfo );
* Get the URL for any given article image
* @param $pParamHash pass in full set of data returned from article query
global $gBitSystem, $gThumbSizes;
$thumbHash['mime_image'] = FALSE;
if ( !empty( $pParamHash['image_attachment_path'] )) {
$thumbHash['source_file'] = $pParamHash['image_attachment_path'];
} elseif ( !empty( $pParamHash['has_topic_image'] ) && $pParamHash['has_topic_image'] == 'y' ) {
* Removes currently loaded article
* @return bool TRUE on success, FALSE on failure
$this->mDb->StartTrans();
$query = "DELETE FROM `". BIT_DB_PREFIX. "articles` WHERE `content_id` = ?";
$result = $this->mDb->query( $query, array( $this->mContentId ) );
$this->mDb->CompleteTrans();
$this->mDb->RollbackTrans();
* Check if there is an article loaded
* @return bool TRUE on success, FALSE on failure
* This function generates a list of records from the liberty_content database for use in a list page
* @param $pParamHash contains an array of conditions to sort by
* @return array of articles
public function getList(&$pParamHash)
global $gBitSystem, $gBitUser, $gLibertySystem;
if ( empty( $pParamHash['sort_mode'] ) ) {
// no idea what this is supposed to do
//$pParamHash['sort_mode'] = $gBitSystem->isFeatureActive('articles_auto_approve') ? 'order_key_desc' : 'publish_date_desc';
$pParamHash['sort_mode'] = 'publish_date_desc';
$this->getServicesSql( 'content_list_sql_function', $selectSql, $joinSql, $whereSql, $bindVars, NULL, $pParamHash );
$find = $pParamHash['find'];
// you can use an array of articles
$whereSql .= " AND UPPER( lc.`title` ) LIKE ? ";
} elseif ( @$this->verifyId( $pParamHash['user_id'] ) ) {
$whereSql .= " AND lc.`user_id` = ? ";
$bindVars[] = (int) $pParamHash['user_id'];
if ( @$this->verifyId( $pParamHash['status_id'] ) ) {
$whereSql .= " AND a.`status_id` = ? ";
$bindVars[] = $pParamHash['status_id'];
if ( @$this->verifyId( $pParamHash['type_id'] ) ) {
$whereSql .= " AND a.`article_type_id` = ? ";
$bindVars[] = (int) $pParamHash['type_id'];
// TODO: we need to check if the article wants to be viewed before / after respective dates
// someone better at SQL please get this working without an additional db call - xing
$now = $gBitSystem->getUTCTime();
if ( !empty( $pParamHash['show_future'] ) && !empty( $pParamHash['show_expired'] ) && $gBitUser->hasPermission( 'p_articles_admin' )) {
// this will show all articles at once - future, current and expired
} elseif ( !empty( $pParamHash['show_future'] ) && $gBitUser->hasPermission( 'p_articles_admin' )) {
$whereSql .= " AND ( a.`expire_date` > ? OR atype.`show_post_expire` = ? ) ";
$bindVars[] = (int) $now;
} elseif ( !empty( $pParamHash['show_expired'] ) && $gBitUser->hasPermission( 'p_articles_admin' )) {
$whereSql .= " AND ( a.`publish_date` < ? OR atype.`show_pre_publ` = ? ) ";
$bindVars[] = (int) $now;
} elseif ( !empty( $pParamHash['get_future'] )) {
// if we're trying to view these articles, we better have the perms to do so
if ( !$gBitUser->hasPermission( 'p_articles_admin' )) {
$whereSql .= " AND a.`publish_date` > ?";
$bindVars[] = (int) $now;
} elseif ( !empty( $pParamHash['get_expired'] )) {
// show only expired articles
// if we're trying to view these articles, we better have the perms to do so
if ( !$gBitUser->hasPermission( 'p_articles_admin' )) {
$whereSql .= " AND a.`expire_date` < ? ";
$bindVars[] = (int) $now;
// hide future and expired articles - this is the default behaviour
// we need all these AND and ORs to ensure that other conditions such as status_id are respected as well
$whereSql .= " AND (( a.`publish_date` > a.`expire_date` ) OR (( a.`publish_date` < ? OR atype.`show_pre_publ` = ? ) AND ( a.`expire_date` > ? OR atype.`show_post_expire` = ? ))) ";
$bindVars[] = (int) $now;
$bindVars[] = (int) $now;
if ( @$this->verifyId( $pParamHash['topic_id'] ) ) {
$whereSql .= " AND a.`topic_id` = ? ";
$bindVars[] = (int) $pParamHash['topic_id'];
} elseif ( !empty( $pParamHash['topic'] ) ) {
$whereSql .= " AND UPPER( atopic.`topic_name` ) = ? ";
$whereSql .= " AND ( atopic.`active_topic` != 'n' OR atopic.`active_topic` IS NULL ) ";
//$whereSql .= " AND atopic.`active_topic` != 'n' ";
// Oracle is very particular about naming multiple columns, so need to explicity name them ORA-00918: column ambiguously defined
a.`article_id`, a.`description`, a.`author_name`, a.`publish_date`, a.`expire_date`, a.`rating`,
atopic.`topic_id`, atopic.`topic_name`, atopic.`has_topic_image`, atopic.`active_topic`,
astatus.`status_id`, astatus.`status_name`,
atype.*, lc.*, la.`attachment_id` AS `primary_attachment_id`, lf.`file_name` AS `image_attachment_path` $selectSql
INNER JOIN `". BIT_DB_PREFIX. "liberty_content` lc ON( lc.`content_id` = a.`content_id` )
INNER JOIN `". BIT_DB_PREFIX. "article_status` astatus ON( astatus.`status_id` = a.`status_id` )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "liberty_content_hits` lch ON( lc.`content_id` = lch.`content_id` )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "article_topics` atopic ON( atopic.`topic_id` = a.`topic_id` )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "article_types` atype ON( atype.`article_type_id` = a.`article_type_id` )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "liberty_attachments` la ON( la.`content_id` = lc.`content_id` AND la.`is_primary` = 'y' )
LEFT OUTER JOIN `". BIT_DB_PREFIX. "liberty_files` lf ON( lf.`file_id` = la.`foreign_id` )
WHERE lc.`content_type_guid` = ? $whereSql
ORDER BY ". $this->mDb->convertSortmode( $pParamHash['sort_mode'] );
$query_cant = "SELECT COUNT( * )FROM `". BIT_DB_PREFIX. "articles` a
INNER JOIN `". BIT_DB_PREFIX. "liberty_content` lc ON lc.`content_id` = a.`content_id`
LEFT OUTER JOIN `". BIT_DB_PREFIX. "article_topics` atopic ON atopic.`topic_id` = a.`topic_id` $joinSql
LEFT OUTER JOIN `". BIT_DB_PREFIX. "article_types` atype ON atype.`article_type_id` = a.`article_type_id`
WHERE lc.`content_type_guid` = ? $whereSql";
$result = $this->mDb->query( $query, $bindVars, $pParamHash['max_records'], $pParamHash['offset'] );
while ( $res = $result->fetchRow() ) {
$res = array_merge( $this->parseSplit( $res, $gBitSystem->getConfig( 'articles_description_length', 500 )), $res );
$res['thumbnail_url'] = static::getImageThumbnails( $res );
$res['num_comments'] = $comment->getNumComments( $res['content_id'] );
$res['display_url'] = self::getDisplayUrlFromHash( $res );
// fetch the primary attachment that we can display the file on the front page if needed
$res['primary_attachment'] = LibertyMime::loadAttachment( $res['primary_attachment_id'] );
$pParamHash["cant"] = $this->mDb->getOne( $query_cant, $bindVars );
* Returns include file that will setup vars for display
* @return the fully specified path to file to be included
return ARTICLES_PKG_PATH. "display_article_inc.php";
* Get a list of articles that are to be published in the future
* @param array $pParamHash contains listing options - same as getList()
* @return array of articles
$pParamHash['get_future'] = TRUE;
return( $this->getList( $pParamHash ));
* Get list of articles that have expired and are not displayed on the site anymore
* @param array $pParamHash contains listing options - same as getList()
* @return array of articles
$pParamHash['get_expired'] = TRUE;
return( $this->getList( $pParamHash ));
* Generates the URL to the article
* @return the link to the full article
if ( @BitBase::verifyId( $pParamHash['article_id'] ) ) {
if ( $gBitSystem->isFeatureActive( 'pretty_urls_extended' ) ) {
// Not needed since it's a number: $ret = ARTICLES_PKG_URL."view/".$this->mArticleId;
$ret = ARTICLES_PKG_URL. $pParamHash['article_id'];
} elseif ( $gBitSystem->isFeatureActive( 'pretty_urls' ) ) {
$ret = ARTICLES_PKG_URL. $pParamHash['article_id'];
$ret = ARTICLES_PKG_URL. "read.php?article_id=". $pParamHash['article_id'];
* Function that returns link to display an image
* @return the url to display the gallery.
$info = array( 'article_id' => $this->mArticleId );
return self::getDisplayUrlFromHash( $info );
* get a list of all available statuses
* @return an array of available statuses
$result = $gBitSystem->mDb->query( $query );
return $result->getRows();
* set the status of an article
* @param $pStatusId new status id of the article
* @param $pArticleId of the article that is being changed - if not set, it will attemtp to change the currently loaded article
* @return new status of article on success - else returns NULL
public function setStatus($pStatusId, $pArticleId = NULL, $pContentId = NULL)
$validStatuses = array( ARTICLE_STATUS_DENIED, ARTICLE_STATUS_DRAFT, ARTICLE_STATUS_PENDING, ARTICLE_STATUS_APPROVED, ARTICLE_STATUS_RETIRED );
if ( !in_array( $pStatusId, $validStatuses ) ) {
$this->mErrors[] = "Invalid article status";
if ( empty( $pArticleId ) && $this->isValid() ) {
if ( @$this->verifyId( $pArticleId ) ) {
$sql = "UPDATE `". BIT_DB_PREFIX. "articles` SET `status_id` = ? WHERE `article_id` = ?";
$rs = $this->mDb->query( $sql, array( $pStatusId, $pArticleId ));
// Calling the index function for approved articles ...
if ( $gBitSystem->isPackageActive( 'search' ) ) {
include_once( SEARCH_PKG_PATH. 'refresh_functions.php' );
if ($pStatusId == ARTICLE_STATUS_APPROVED) {
} elseif (!$pStatusId == ARTICLE_STATUS_RETIRED) {
delete_index($pContentId); // delete it from the search index unless retired ...
|