diff options
| author | modela bitweaver <spiderr@bitweaver.org> | 2021-02-02 01:17:09 -0500 |
|---|---|---|
| committer | modela bitweaver <spiderr@bitweaver.org> | 2021-02-02 01:17:09 -0500 |
| commit | 4abf677ae556f0cf7e08d377df1e0ffb9041267a (patch) | |
| tree | 92023c1799a04fe82398beb9436d4e67f7688658 /includes | |
| parent | 8ad7ef38d30923bb0b8cb5bce381dc4ce87052a4 (diff) | |
| download | liberty-4abf677ae556f0cf7e08d377df1e0ffb9041267a.tar.gz liberty-4abf677ae556f0cf7e08d377df1e0ffb9041267a.tar.bz2 liberty-4abf677ae556f0cf7e08d377df1e0ffb9041267a.zip | |
move _inc and _lib to includes/ and use PKG_INCLUDE_PATH constants
Diffstat (limited to 'includes')
| -rw-r--r-- | includes/calculate_max_upload_inc.php | 25 | ||||
| -rw-r--r-- | includes/classes/LibertyAttachable.php | 418 | ||||
| -rw-r--r-- | includes/classes/LibertyBase.php | 127 | ||||
| -rw-r--r-- | includes/classes/LibertyComment.php | 708 | ||||
| -rw-r--r-- | includes/classes/LibertyContent.php | 3844 | ||||
| -rw-r--r-- | includes/classes/LibertyMime.php | 1267 | ||||
| -rw-r--r-- | includes/classes/LibertyStructure.php | 1153 | ||||
| -rw-r--r-- | includes/classes/LibertySystem.php | 865 | ||||
| -rw-r--r-- | includes/comments_inc.php | 368 | ||||
| -rw-r--r-- | includes/content_history_inc.php | 86 | ||||
| -rw-r--r-- | includes/display_content_inc.php | 17 | ||||
| -rw-r--r-- | includes/display_structure_inc.php | 22 | ||||
| -rw-r--r-- | includes/edit_help_inc.php | 68 | ||||
| -rw-r--r-- | includes/edit_storage_inc.php | 55 | ||||
| -rw-r--r-- | includes/edit_structure_inc.php | 126 | ||||
| -rw-r--r-- | includes/get_content_list_inc.php | 93 | ||||
| -rw-r--r-- | includes/help_format_tikiwiki_inc.php | 239 | ||||
| -rw-r--r-- | includes/liberty_lib.php | 939 | ||||
| -rw-r--r-- | includes/lookup_content_inc.php | 40 |
19 files changed, 10460 insertions, 0 deletions
diff --git a/includes/calculate_max_upload_inc.php b/includes/calculate_max_upload_inc.php new file mode 100644 index 0000000..ee86071 --- /dev/null +++ b/includes/calculate_max_upload_inc.php @@ -0,0 +1,25 @@ +<?php +/** + * @version $Header$ + * + * settings that are useful to know about at upload time + * + * @package liberty + * @subpackage functions + */ +$postMax = str_replace( 'M', '', ini_get( 'post_max_size' )); +$uploadMax = str_replace( 'M', '', ini_get( 'upload_max_filesize' ) ); + +if( $postMax < $uploadMax ) { + $uploadMax = $postMax; +} + +/** + * calculate user quota + */ +if( $gBitSystem->isPackageActive( 'quota' ) ) { + require_once( QUOTA_PKG_INCLUDE_PATH.'calculate_quota_inc.php' ); +} + +$gBitSmarty->assignByRef( 'uploadMax', $uploadMax ); +?> diff --git a/includes/classes/LibertyAttachable.php b/includes/classes/LibertyAttachable.php new file mode 100644 index 0000000..9c365fa --- /dev/null +++ b/includes/classes/LibertyAttachable.php @@ -0,0 +1,418 @@ +<?php +/** + * Management of Liberty Content + * + * @package liberty + * @version $Header$ + * @author spider <spider@steelsun.com> + */ +// +----------------------------------------------------------------------+ +// | Copyright (c) 2004, bitweaver.org +// +----------------------------------------------------------------------+ +// | 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 +// | +// | For comments, please use phpdocu.sourceforge.net documentation standards!!! +// | -> see http://phpdocu.sourceforge.net/ +// +----------------------------------------------------------------------+ +// | Authors: spider <spider@steelsun.com> +// +----------------------------------------------------------------------+ + +/** + * required setup + */ +require_once( LIBERTY_PKG_CLASS_PATH.'LibertyContent.php' ); + +/** + * LibertyAttachable class + * + * @package liberty + */ +class LibertyAttachable extends LibertyContent { + public $mContentId; + public $mStorage; + + function LibertyAttachable() { + parent::__construct(); + } + + // {{{ =================== Deprecated Methods ==================== + /** + * TODO: This code is old and is not used by any package in the bitweaver CVS anymore. + * We will clean up this code as soon as we are sure that noone is using this code. + * Please look for the closing tripple '}' brackets to see where this section ends. + */ + + /** + * fully load content and insert any attachments in $this->mStorage + * allow an optional content_id to be passed in to ease legacy lib style objects (like blogs, articles, etc.) + * + * @param array $pContentId + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + * @deprecated deprecated since version 2.1.0-beta + */ + + /** + * TODO: This code is old and is not used by any package in the bitweaver CVS anymore. + * We will clean up this code as soon as we migrated all legacy code + */ + function load( $pContentId = NULL, $pPluginParams = NULL ) { + //deprecated( "This method has been replaced by a method in LibertyMime. Please try to migrate your code." ); + // assume a derived class has joined on the liberty_content table, and loaded it's columns already. + global $gLibertySystem; + $conId = ( @$this->verifyId( $pContentId ) ? $pContentId : $this->mContentId ); + + if( @$this->verifyId( $conId ) ) { + LibertyContent::load( $conId ); + $query = " + SELECT * + FROM `".BIT_DB_PREFIX."liberty_attachments` la + WHERE la.`content_id`=? ORDER BY la.`pos` ASC, la.`attachment_id` ASC"; + if( $result = $this->mDb->query( $query,array( (int)$conId ))) { + $this->mStorage = array(); + while( $row = $result->fetchRow() ) { + if( $func = $gLibertySystem->getPluginFunction( $row['attachment_plugin_guid'], 'load_function', 'mime' )) { + // this dummy is needed for forward compatability with LibertyMime plugins + $dummy = array(); + $this->mStorage[$row['attachment_id']] = $func( $row, $dummy ); + //$this->mStorage[$row['attachment_id']]['is_primary'] = !empty( $row['primary_attachment_id'] ); + } else { + print "No load_function for ".$row['attachment_plugin_guid']." ".$gLibertySystem->mPlugins[$row['attachment_plugin_guid']]; + } + } + } + } + return( TRUE ); + } + + /* store -- Stores any attachments + * + * pass $pParamHash['liberty_attachable']['skip_content_store'] == TRUE + * to avoid the underlying content store and simply store the attachments. + * + * verify() will shove things to store into $pParamHash['STORAGE'] to be + * gobbled up in this function. + * + * pass $pParamHash['liberty_attachable']['auto_primary'] == FALSE to turn off the auto + * primary on first attachment feature for a content type. + * + * @param hash $pParamHash The hash of arguments + * + * @deprecated deprecated since version 2.1.0-beta + */ + + /** + * TODO: This code is old and is not used by any package in the bitweaver CVS anymore. + * We will clean up this code as soon as we migrated all legacy code + */ + function store( &$pParamHash ) { + //deprecated( "This method has been replaced by a method in LibertyMime. Please try to migrate your code." ); + global $gLibertySystem, $gBitSystem, $gBitUser; + $this->StartTrans(); + if( LibertyAttachable::verify( $pParamHash ) && ( isset($pParamHash['skip_content_store']) || LibertyContent::store( $pParamHash ) ) ) { + + if(!empty( $pParamHash['STORAGE'] ) && count( $pParamHash['STORAGE'] ) ) { + foreach( array_keys( $pParamHash['STORAGE'] ) as $guid ) { + $storeRows = &$pParamHash['STORAGE'][$guid]; // short hand variable assignment + // If it is empty then nothing more to do. Avoid error in foreach. + if (empty($storeRows)) { + continue; + } + foreach( $storeRows as $key => $value ) { + $storeRow = &$pParamHash['STORAGE'][$guid][$key]; + $storeRow['plugin_guid'] = $guid; + + if (!@BitBase::verifyId($pParamHash['content_id'])) { + $storeRow['content_id'] = NULL; + } else { + $storeRow['content_id'] = $pParamHash['content_id']; // copy in content_id + } + + if (!empty($pParamHash['user_id'])) { + $storeRow['user_id'] = $pParamHash['user_id']; // copy in the user_id + } else { + $storeRow['user_id'] = $gBitUser->mUserId; + } + + // do we have a verify function for this storage type, and do things verify? + $verifyFunc = $gLibertySystem->getPluginFunction( $guid, 'verify_function' ); + if( $verifyFunc && $verifyFunc( $storeRow ) ) { + // For backwards compatibility with a single upload. + if( @BitBase::verifyId( $pParamHash['attachment_id'] )) { + $storeRow['upload']['attachment_id'] = $storeRow['attachment_id'] = $pParamHash['attachment_id']; + } else if ( !isset($storeRow['skip_insert'] ) ) { + if ( defined( 'LINKED_ATTACHMENTS' ) && @BitBase::verifyId( $pParamHash['content_id'] ) ) { + $storeRow['upload']['attachment_id'] = $storeRow['attachment_id'] = $pParamHash['content_id']; + } else { + $storeRow['upload']['attachment_id'] = $storeRow['attachment_id'] = + defined( 'LINKED_ATTACHMENTS' ) ? $this->mDb->GenID( 'liberty_content_id_seq') : $this->mDb->GenID( 'liberty_attachments_id_seq' ); + } + } + + // if we have uploaded a file, we can take care of that generically + if( !empty( $storeRow['upload'] ) && is_array( $storeRow['upload'] ) && !empty( $storeRow['upload']['size'] ) ) { + if( empty( $storeRow['upload']['type'] ) ) { + $ext = substr( $storeRow['upload']['name'], strrpos( $storeRow['upload']['name'], '.' ) + 1 ); + $storeRow['upload']['type'] = $gBitSystem->lookupMimeType( $ext ); + } + $storeRow['upload']['dest_branch'] = $this->getStorageBranch( $storeRow['attachment_id'], $storeRow['user_id'], $this->getStorageSubDirName() ); + if (!empty( $pParamHash['thumbnail_sizes'] ) ) { + $storeRow['upload']['thumbnail_sizes'] = $pParamHash['thumbnail_sizes']; + } + $storagePath = liberty_process_upload( $storeRow['upload'] ); + // We're gonna store to local file system & liberty_files table + if( empty( $storagePath ) ) { + $this->mErrors['file'] = tra( "Could not store file" ).": ".$storeRow['upload']['name'].'.'; + $storeRow['attachment_id'] = NULL; + $storeRow['upload']['attachment_id'] = NULL; + } else { + $storeRow['upload']['dest_file_path'] = $storagePath; + } + } + + if( @BitBase::verifyId( $storeRow['attachment_id'] ) && $storeFunc = $gLibertySystem->getPluginFunction( $storeRow['plugin_guid'], 'store_function' )) { + $this->mStorage = $storeFunc( $storeRow ); + } + + // don't insert if we already have an entry with this attachment_id + if( @BitBase::verifyId( $storeRow['attachment_id'] ) && !isset( $storeRow['skip_insert'] ) && !LibertyMime::loadAttachment( $storeRow['attachment_id'] )) { + $sql = "INSERT INTO `".BIT_DB_PREFIX."liberty_attachments` ( `content_id`, `attachment_id`, `attachment_plugin_guid`, `foreign_id`, `user_id` ) VALUES ( ?, ?, ?, ?, ? )"; + $rs = $this->mDb->query( $sql, array( $storeRow['content_id'], $storeRow['attachment_id'], $storeRow['plugin_guid'], (int)$storeRow['foreign_id'], $storeRow['user_id'] ) ); + } + } + } + } + } + + // set the primary attachment id + $this->setPrimaryAttachment( + $pParamHash['liberty_attachments']['primary'], + $pParamHash['content_id'], + empty( $pParamHash['liberty_attachments']['auto_primary'] ) || $pParamHash['liberty_attachments']['auto_primary'] ? TRUE : FALSE + ); + } + $this->CompleteTrans(); + + return( count( $this->mErrors ) == 0 ); + } + + /** + * verifyAttachment + * + * @param array $pParamHash + * @param array $pFile + * @param array $pKey + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + * @deprecated deprecated since version 2.1.0-beta + */ + function verifyAttachment( &$pParamHash, $pFile, $pKey ) { + //deprecated( "This method has been replaced by a method in LibertyMime. Please try to migrate your code." ); + global $gBitSystem, $gBitUser, $gLibertySystem; + + if( !empty( $pFile ) && !empty( $pFile['size'] ) ) { + if( empty( $pParamHash['storage_guid'] )) { + // only file format storage available at present + $pParamHash['storage_guid'] = $storageGuid = PLUGIN_GUID_BIT_FILES; + } else { + $storageGuid = $pParamHash['storage_guid']; + } + + if( !empty( $pFile['size'] ) ) { + $this->extractMetaData( $pParamHash, $pFile ); + // meta data may be stupid and have stuffed title with all spaces + if( !empty( $pParamHash['title'] ) ) { + $pParamHash['title'] = trim( $pParamHash['title'] ); + } + + // let's add a default title + if( empty( $pParamHash['title'] ) && !empty( $pFile['name'] ) ) { + if( preg_match( '/^[A-Z]:\\\/', $pFile['name'] ) ) { + // MSIE shit file names if passthrough via gigaupload, etc. + // basename will not work - see http://us3.php.net/manual/en/function.basename.php + $tmp = preg_split("#[\\\]#",$pFile['name']); + $defaultName = $tmp[count($tmp) - 1]; + } elseif( strpos( '.', $pFile['name'] ) ) { + list( $defaultName, $ext ) = explode( '.', $pFile['name'] ); + } else { + $defaultName = $pFile['name']; + } + $pParamHash['title'] = str_replace( '_', ' ', substr( $defaultName, 0, strrpos( $defaultName, '.' ) ) ); + } + + + if ( !is_windows() ) { + list( $pFile['name'], $pFile['type'] ) = $gBitSystem->verifyFileExtension( $pFile['tmp_name'], $pFile['name'] ); + } else { + //$pFile['type'] = $gBitSystem->verifyMimeType( $pFile['tmp_name'] ); + } + // clean out crap that can make life difficult in server maintenance + $cleanedBaseName = preg_replace( '/[&\%:\/\\\]/', '', substr( $pFile['name'], 0, strrpos( $pFile['name'], '.' ) ) ); + $pFile['dest_base_name'] = $cleanedBaseName; + $pFile['source_file'] = $pFile['tmp_name']; + // lowercase all file extensions + $pFile['name'] = $cleanedBaseName.strtolower( substr( $pFile['name'], strrpos( $pFile['name'], '.' ) ) ); + if (!isset($pParamHash['STORAGE'][$storageGuid])) { + $pParamHash['STORAGE'][$storageGuid] = array(); + } + $pParamHash['STORAGE'][$storageGuid][$pKey] = array('upload' => &$pFile); + } + } + } + + /** + * verify - standard API method, with a twist. It will gobble up anything in $_FILES if available, unless an array of arrays is passed in to $pParamHash['_files_override'] + * + * @access private + * @author Christian Fowler<spider@steelsun.com> + * @param $pParamHash + * @return FALSE if errors were present, TRUE meaning object is ready to store + * @deprecated deprecated since version 2.1.0-beta + */ + function verify( &$pParamHash ) { + //deprecated( "This method has been replaced by a method in LibertyMime. Please try to migrate your code." ); + global $gBitSystem, $gBitUser; + // check to see if we have any files to upload + if( isset( $pParamHash['_files_override'] ) ) { + // we have been passed in a manually stuffed files attachment, such as a custom uploader would have done. + // process this, and skip over $_FILES + $uploads = $pParamHash['_files_override']; + } elseif( !empty( $_FILES ) ) { + // we have some _FILES hanging around we will gobble up. This is inherently dagnerous chewing up a _FILES like this as + // it can cause premature storing of a _FILE if you are trying to store multiple pieces of content at once. + foreach( $_FILES as $key => $file ) { + if( !empty( $file['name'] )) { + $uploads[$key] = $file; + } + } + } + + // don't check for p_liberty_attach_attachments permission on bitpermuser class so registration with avatar upload works + if( strtolower( get_class( $this )) == 'bitpermuser' ) { + $pParamHash['no_perm_check'] = TRUE; + } + + // check for the required permissions to upload a file to the liberty attachments area + if( !empty( $uploads ) && empty( $pParamHash['no_perm_check'] )) { + if( !$gBitUser->hasPermission( 'p_liberty_attach_attachments' )) { + $this->mErrors['permission'] = tra( 'You do not have permission to upload attachments.' ); + } + } + + if( !empty( $pParamHash['attachment_id'] ) && !$this->verifyId( $pParamHash['attachment_id'] ) ) { + $this->mErrors['file'] = tra('System Error: Non-numeric storage_id.'); + } + + if( empty( $pParamHash['user_id'] ) ) { + // storage is always owned by the user that uploaded it! + // er... or at least admin if somehow we have a NULL mUserId - anon uploads maybe? + $pParamHash['user_id'] = @$this->verifyId( $gBitUser->mUserId ) ? $gBitUser->mUserId : ROOT_USER_ID; + } + if( empty( $pParamHash['process_storage'] ) ) { + $pParamHash['process_storage'] = NULL; + } + + if( empty( $pParamHash['subdir'] ) ) { + $pParamHash['subdir'] = 'files'; + } + + if( !empty( $uploads ) ) { + foreach( array_keys( $uploads ) as $f ) { + $this->verifyAttachment( $pParamHash, $uploads[$f], $f ); + } + } + + // primary attachment. Allow 'none' to clear the primary. + if( !@BitBase::verifyId( $pParamHash['liberty_attachments']['primary'] ) && ( empty( $pParamHash['liberty_attachments']['primary'] ) || $pParamHash['liberty_attachments']['primary'] != 'none' ) ) { + $pParamHash['liberty_attachments']['primary'] = NULL; + } + + // if we have an error we get them all by checking parent classes for additional errors + if( count( $this->mErrors ) > 0 ){ + parent::verify( $pParamHash ); + } + + return ( count( $this->mErrors ) == 0 ); + } + + /** + * extractMetaData extract meta data from images + * + * @param array $pParamHash + * @param array $pFile + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + * @deprecated deprecated since version 2.1.0-beta + */ + function extractMetaData( &$pParamHash, &$pFile ) { + //deprecated( "This method has been replaced by a method in LibertyMime. Please try to migrate your code." ); + // Process a JPEG , jpeg_metadata_tk REQUIRES short_tags because that is the way it was written. feel free to fix something. XOXO spiderr + if( ini_get( 'short_open_tag' ) && function_exists( 'exif_read_data' ) && !empty( $pFile['tmp_name'] ) && strpos( strtolower($pFile['type']), 'jpeg' ) !== FALSE ) { + $exifHash = @exif_read_data( $pFile['tmp_name'], 0, true); + + // Change: Allow this example file to be easily relocatable - as of version 1.11 + require_once UTIL_PKG_INCLUDE_PATH.'jpeg_metadata_tk/JPEG.php'; + require_once UTIL_PKG_INCLUDE_PATH.'jpeg_metadata_tk/JFIF.php'; + require_once UTIL_PKG_INCLUDE_PATH.'jpeg_metadata_tk/PictureInfo.php'; + require_once UTIL_PKG_INCLUDE_PATH.'jpeg_metadata_tk/XMP.php'; + require_once UTIL_PKG_INCLUDE_PATH.'jpeg_metadata_tk/EXIF.php'; + + // Retrieve the header information from the JPEG file + $jpeg_header_data = get_jpeg_header_data( $pFile['tmp_name'] ); + + // Retrieve EXIF information from the JPEG file + $Exif_array = get_EXIF_JPEG( $pFile['tmp_name'] ); + + // Retrieve XMP information from the JPEG file + $XMP_array = read_XMP_array_from_text( get_XMP_text( $jpeg_header_data ) ); + + // Retrieve Photoshop IRB information from the JPEG file + $IRB_array = get_Photoshop_IRB( $jpeg_header_data ); + if( !empty( $exifHash['IFD0']['Software'] ) && preg_match( '/photoshop/i', $exifHash['IFD0']['Software'] ) ) { + require_once UTIL_PKG_INCLUDE_PATH.'jpeg_metadata_tk/Photoshop_File_Info.php'; + // Retrieve Photoshop File Info from the three previous arrays + $psFileInfo = get_photoshop_file_info( $Exif_array, $XMP_array, $IRB_array ); + + if( !empty( $psFileInfo['headline'] ) ) { + if( empty( $pParamHash['title'] ) ) { + $pParamHash['title'] = $psFileInfo['headline']; + } elseif( empty( $pParamHash['edit'] ) && !$this->getField( 'data' ) && $pParamHash['title'] != $psFileInfo['headline'] ) { + $pParamHash['edit'] = $psFileInfo['headline']; + } + } + if( !empty( $psFileInfo['caption'] ) ) { + if( empty( $pParamHash['title'] ) ) { + $pParamHash['title'] = $psFileInfo['caption']; + } elseif( empty( $pParamHash['edit'] ) && !$this->getField( 'data' ) && $pParamHash['title'] != $psFileInfo['caption'] ) { + $pParamHash['edit'] = $psFileInfo['caption']; + } + } + } + + if( !empty( $exifHash['EXIF']['DateTimeOriginal'] ) ) { + $pParamHash['event_time'] = strtotime( $exifHash['EXIF']['DateTimeOriginal'] ); + } + + if( !empty( $exifHash['IFD0']['ImageDescription'] ) ) { + if( empty( $pParamHash['title'] ) ) { + $pParamHash['title'] = $exifHash['IFD0']['ImageDescription']; + } elseif( empty( $pParamHash['edit'] ) && !$this->getField( 'data' ) && $pParamHash['title'] != $exifHash['IFD0']['ImageDescription'] ) { + $pParamHash['edit'] = $exifHash['IFD0']['ImageDescription']; + } + } + } + } + + // }}} + + +} + +// FIXME: this is really dirty and needs to go away from here +// make sure LibertyMime is available during this transition phase +// we need to call this down here since LM extends LA and can't be included before LA is available +require_once( LIBERTY_PKG_CLASS_PATH.'LibertyMime.php' ); + +/* vim: :set fdm=marker : */ +?> diff --git a/includes/classes/LibertyBase.php b/includes/classes/LibertyBase.php new file mode 100644 index 0000000..1825827 --- /dev/null +++ b/includes/classes/LibertyBase.php @@ -0,0 +1,127 @@ +<?php +/** + * Base class for Management of Liberty Content + * + * @package liberty + * @version $Header$ + * @author spider <spider@steelsun.com> + */ +// +----------------------------------------------------------------------+ +// | Copyright (c) 2004, bitweaver.org +// +----------------------------------------------------------------------+ +// | 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 +// | +// | For comments, please use phpdocu.sourceforge.net documentation standards!!! +// | -> see http://phpdocu.sourceforge.net/ +// +----------------------------------------------------------------------+ +// | Authors: spider <spider@steelsun.com> +// +----------------------------------------------------------------------+ + +/** + * required setup + */ +require_once( LIBERTY_PKG_INCLUDE_PATH.'liberty_lib.php' ); +require_once( KERNEL_PKG_CLASS_PATH.'BitBase.php' ); + +/** + * Virtual base class (as much as one can have such things in PHP) for all + * derived bitweaver classes that manage content. + * + * @package liberty + */ +class LibertyBase extends BitBase { + + /** + * Constructor building on BitBase object + * + * Object need to init the database connection early + * Database will be linked via a previously activated BitDb object + * which will provide the mDb pointer to that database + */ + function __construct() { + parent::__construct(); + } + + /** + * given a content_type_guid this will return an object of the proper type + * + * @param the content type to be loaded + */ + function getLibertyClass($pContentTypeGuid) { + // We can abuse getLibertyObject to do the work + $ret = LibertyBase::getLibertyObject('1', $pContentTypeGuid, FALSE); + // Make sure we don't have a content_id set though. + unset($ret->mContentId); + return $ret; + } + + /** + * Given a content_id, this will return and object of the proper type + * + * @param integer content_id of the object to be returned + * @param string optional content_type_guid of pConId. This will save a select if you happen to have this info. If not, this method will look it up for you. + * @param call load on the content. Defaults to true. + * @returns object of the appropriate content type class + */ + public static function getLibertyObject( $pContentId, $pContentTypeGuid=NULL, $pLoadFromCache = TRUE ) { + $ret = NULL; + global $gLibertySystem, $gBitUser, $gBitSystem; + + if( static::verifyId( $pContentId ) ) { + // remove non integer bits from structure_id and content_id requests + // can happen with period's at the end of url's that are email'ed around + $typeClass = NULL; + $pContentId = preg_replace( '/[\D]/', '', $pContentId ); + if( empty( $pContentTypeGuid ) ) { + $pContentTypeGuid = $gLibertySystem->mDb->getOne( "SELECT `content_type_guid` FROM `".BIT_DB_PREFIX."liberty_content` WHERE `content_id`=?", array( $pContentId ), NULL, NULL, 3600 ); + } + if( !empty( $pContentTypeGuid ) && isset( $gLibertySystem->mContentTypes[$pContentTypeGuid] ) ) { + $typeClass = $gLibertySystem->getContentClassName( $pContentTypeGuid ); + } + if( $pLoadFromCache && ($ret = static::loadFromCache( $pContentId, $typeClass )) ) { + $ret->mCacheObject = TRUE; + } else { + if( $typeClass ) { + $creator = new $typeClass(); + $ret = $creator->getNewObject( $typeClass, $pContentId, $pLoadFromCache ); + $ret->setCacheableObject( FALSE ); + $ret->clearFromCache(); + } + } + } + return $ret; + } + + /** + * Simple method to create a given class with a specified primary_id. The purpose of a method is to allow for derived classes to override as necessary. + * + * @param string class to be created + * @param integer id from the secondary table of the object to be returned + * @param call load on the content. Defaults to true. + * @returns object of the appropriate content type class + */ + public static function getNewObjectById( $pClass, $pPrimaryId, $pLoadFromCache=TRUE ) { + if( $ret = new $pClass( $pPrimaryId ) ) { + $ret->load(); + } + return $ret; + } + + /** + * Simple method to create a given class with a specified content_id. The purpose of a method is to allow for derived classes to override as necessary. + * + * @param string class to be created + * @param integer content_id of the object to be returned + * @param call load on the content. Defaults to true. + * @returns object of the appropriate content type class + */ + public static function getNewObject( $pClass, $pContentId, $pLoadFromCache=TRUE ) { + if( $ret = new $pClass( NULL, $pContentId ) ) { + $ret->load(); + } + return $ret; + } +} + +?> diff --git a/includes/classes/LibertyComment.php b/includes/classes/LibertyComment.php new file mode 100644 index 0000000..db2f82d --- /dev/null +++ b/includes/classes/LibertyComment.php @@ -0,0 +1,708 @@ +<?php +/** + * Management of Liberty Content + * + * @package liberty + * @version $Header$ + * @author spider <spider@steelsun.com> + */ + +/** + * required setup + */ +require_once( LIBERTY_PKG_CLASS_PATH.'LibertyMime.php' ); + +define( 'BITCOMMENT_CONTENT_TYPE_GUID', 'bitcomment' ); + +/** + * Handles all comments which are actual content objects + * + * @package liberty + */ + +class LibertyComment extends LibertyMime { + public $mCommentId; + + function __construct($pCommentId = NULL, $pContentId = NULL, $pInfo = NULL) { + parent::__construct(); + $this->registerContentType( BITCOMMENT_CONTENT_TYPE_GUID, array( + 'content_type_guid' => BITCOMMENT_CONTENT_TYPE_GUID, + 'content_name' => 'Comment', + 'handler_class' => 'LibertyComment', + 'handler_package' => 'liberty', + 'handler_file' => 'LibertyComment.php', + 'maintainer_url' => 'http://www.bitweaver.org' + ) ); + $this->mCommentId = (int)$pCommentId; + $this->mContentId = (int)$pContentId; + $this->mInfo = $pInfo; + $this->mContentTypeGuid = BITCOMMENT_CONTENT_TYPE_GUID; + $this->mAdminContentPerm = 'p_liberty_admin_comments'; + $this->mRootObj = NULL; + + if ($this->mCommentId || $this->mContentId) { + $this->loadComment(); + } + } + + + public function __sleep() { + return array_merge( parent::__sleep(), array( 'mCommentId', 'mRootObj' ) ); + } + + function loadComment() { + global $gBitSystem, $gBitUser; + if (!$this->verifyId($this->mCommentId) && !$this->verifyId($this->mContentId)) { + return NULL; + } + + if ($this->mCommentId) { + $mid = 'WHERE lcom.`comment_id` = ?'; + $bindVars = array($this->mCommentId); + } else { + $mid = 'WHERE lc.`content_id` = ?'; + $bindVars = array($this->mContentId); + } + + $joinSql = $selectSql = $whereSql = ''; + $this->getServicesSql( 'content_load_sql_function', $selectSql, $joinSql, $whereSql, $bindVars, $this ); + + $sql = "SELECT lcom.*, lc.*, uu.`email`, uu.`real_name`, uu.`login` $selectSql + FROM `".BIT_DB_PREFIX."liberty_comments` lcom + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON (lcom.`content_id` = lc.`content_id`) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_users` uu ON (lc.`user_id` = uu.`user_id`) $joinSql + $mid $whereSql"; + if( $row = $this->mDb->getRow( $sql, $bindVars ) ) { + $this->mInfo = $row; + $this->mContentId = $row['content_id']; + $this->mCommentId = $row['comment_id']; + + // call parent load for attachment data like other Mime derived classes, only need it if feature is active or admin + if( $gBitSystem->isFeatureActive( 'comments_allow_attachments' ) || $gBitUser->isAdmin() ){ + LibertyMime::load(); + } + } + return count($this->mInfo); + } + + function verifyComment(&$pParamHash) { + global $gBitUser, $gBitSystem; + + /* should be unnecessary + if( !empty( $_REQUEST['format_guid'] )) { + $storeRow['format_guid'] = $_REQUEST['format_guid']; + } + */ + + $pParamHash['content_id'] = (@BitBase::verifyId($this->mContentId) ? $this->mContentId : NULL); + + if( empty( $pParamHash['root_id'] ) && !empty( $pParamHash['comments_parent_id'] ) ) { + $pParamHash['root_id'] = $pParamHash['comments_parent_id']; + } + + if (!$pParamHash['root_id']) { + $this->mErrors['root_id'] = "Missing root id for comment"; + } + + if( empty( $pParamHash['parent_id'] ) ){ + $pParamHash['parent_id'] = (@BitBase::verifyId($this->mInfo['parent_id']) ? $this->mInfo['parent_id'] : (!@BitBase::verifyId($pParamHash['post_comment_reply_id']) ? $pParamHash['comments_parent_id'] : $pParamHash['post_comment_reply_id'])); + } + + if (!$pParamHash['parent_id']) { + $this->mErrors['parent_id'] = "Missing parent id for comment"; + } + + if (empty($pParamHash['anon_name'])) { + $pParamHash['anon_name']=null; + } + + if( !@$gBitUser->verifyCaptcha( $pParamHash['captcha'] ) ) { + $this->mErrors['store'] = tra( 'Incorrect validation code' ); + } + + if( !empty( $pParamHash['comment_title'] ) ){ + $pParamHash['title'] = $pParamHash['comment_title']; + } + + if( !empty( $pParamHash['comment_data'] ) ){ + $pParamHash['edit'] = $pParamHash['comment_data']; + } + + if( empty( $pParamHash['edit'] ) ) { + $this->mErrors['store'] = tra( 'Your comment was empty.' ); + } elseif( !$gBitUser->hasPermission( 'p_liberty_trusted_editor' ) && ($linkCount = preg_match_all( '/http\:\/\//', $pParamHash['edit'], $links )) > $gBitSystem->getConfig( 'liberty_unstrusted_max_http_in_content', 0 ) ) { + $this->mErrors['store'] = tra( 'Links are not allowed.' ); + } else { + $dupeQuery = "SELECT `data` FROM `".BIT_DB_PREFIX."liberty_content` lc INNER JOIN `".BIT_DB_PREFIX."liberty_comments` lcom ON (lc.`content_id`=lcom.`content_id`) WHERE `user_id`=? AND `content_type_guid`='".BITCOMMENT_CONTENT_TYPE_GUID."' AND `ip`=? AND lcom.`root_id`=? ORDER BY `created` DESC"; + if( $lastPostData = $this->mDb->getOne( $dupeQuery, array( $gBitUser->mUserId, $_SERVER['REMOTE_ADDR'], $pParamHash['root_id'] ) ) ) { + if( empty( $this->mCommentId ) && trim( $lastPostData ) == trim( $pParamHash['edit'] ) ) { + $this->mErrors['store'] = tra( 'Duplicate comment.' ); + } + } + } + + // verify attachments are allowed on comments + if( ( isset( $pParamHash['_files_override'] ) || !empty( $_FILES ) ) && !$gBitSystem->isFeatureActive( 'comments_allow_attachments' ) ) { + $this->mErrors['comment_attachments'] = tra( 'Files can not be uploaded with comments.' ); + } + + // if we have an error we get them all by checking parent classes for additional errors + if( count( $this->mErrors ) > 0 ){ + parent::verify( $pParamHash ); + } + + return (count($this->mErrors) == 0); + } + + function storeComment( &$pParamHash ) { + $this->StartTrans(); + if( $this->verifyComment($pParamHash) && LibertyMime::store( $pParamHash ) ) { + if (!$this->mCommentId) { + $this->mCommentId = $this->mDb->GenID( 'liberty_comment_id_seq'); + + if (!empty($pParamHash['parent_id'])) { + $parentComment = new LibertyComment(NULL,$pParamHash['parent_id']); + } + $parent_sequence_forward = ''; + $parent_sequence_reverse = ''; + if (!empty($parentComment->mInfo['thread_forward_sequence'])) { + $parent_sequence_forward = $parentComment->mInfo['thread_forward_sequence']; + $parent_sequence_reverse = $parentComment->mInfo['thread_reverse_sequence']; + } + // if nesting level > 25 deep, put it on level 25 + if (strlen($parent_sequence_forward) > 10*24) { + $parent_sequence_forward = substr($parent_sequence_forward,0,10*24); + } + + $this->mInfo['thread_forward_sequence'] = $parent_sequence_forward . sprintf("%09d.",$this->mCommentId); + $this->mInfo['thread_reverse_sequence'] = strtr($parent_sequence_forward . sprintf("%09d.",$this->mCommentId), + '0123456789', '9876543210'); + + $sql = "INSERT INTO `".BIT_DB_PREFIX."liberty_comments` (`comment_id`, `content_id`, `parent_id`, `root_id`, `anon_name`, `thread_forward_sequence`, `thread_reverse_sequence`) VALUES (?,?,?,?,?,?,?)"; + + $this->mDb->query($sql, array($this->mCommentId, $pParamHash['content_id'], $pParamHash['parent_id'], + $pParamHash['root_id'], $pParamHash['anon_name'], + $this->mInfo['thread_forward_sequence'], $this->mInfo['thread_reverse_sequence'])); + $this->mInfo['parent_id'] = $pParamHash['parent_id']; + $this->mInfo['content_id'] = $pParamHash['content_id']; + $this->mInfo['root_id'] = $pParamHash['root_id']; + $this->mContentId = $pParamHash['content_id']; + } else { + $sql = "UPDATE `".BIT_DB_PREFIX."liberty_comments` SET `parent_id` = ?, `content_id`= ? WHERE `comment_id` = ?"; + $this->mDb->query($sql, array($pParamHash['parent_id'], $pParamHash['content_id'], $this->mCommentId)); + $this->mInfo['parent_id'] = $pParamHash['parent_id']; + $this->mInfo['content_id'] = $pParamHash['content_id']; + $this->mContentId = $pParamHash['content_id']; + } + $this->invokeServices( 'comment_store_function', $pParamHash ); + + } + + $this->CompleteTrans(); + return (count($this->mErrors) == 0); + } + + + // This is a highly specialized method only used for emailing list synchronization. If you don't know anything about this, just move along and live in bliss + // (Hint: see mailing list integreation in boards) + function storeMessageId( $pMessageId ) { + if( $this->isValid() ) { + $this->mDb->query( "UPDATE `".BIT_DB_PREFIX."liberty_comments` SET `message_guid`=? WHERE `content_id`=?", array( $pMessageId, $this->mContentId ) ); + } + } + + // delete a single comment + function deleteComment() { + global $gBitSystem; + $ret = FALSE; + if( $this->isValid() ) { + $this->StartTrans(); + + if( $gBitSystem->isPackageActive( 'boards' ) ) { + // due to foreign key constraints, this has to go in the base class of BitBoardPost + $sql = "DELETE FROM `".BIT_DB_PREFIX."boards_posts` WHERE `comment_id` = ?"; + $rs = $this->mDb->query($sql, array($this->mCommentId ) ); +// $query = "DELETE FROM `".BIT_DB_PREFIX."boards_topics` WHERE `parent_id` = ?"; +// $result = $this->mDb->query( $query, array( $this->getField( 'content_id' ) ) ); + } + + + $sql = "DELETE FROM `".BIT_DB_PREFIX."liberty_comments` WHERE `comment_id` = ?"; + $rs = $this->mDb->query($sql, array($this->mCommentId)); + + /* + * TODO: figureout why this is even here. Mime should handle this and it needs to pass in mandatory attachmentId + * Slated to delete for now - wjames5 + */ + /* + if (method_exists($this,'expungeMetaData')) { + $this->expungeMetaData(); + } + */ + + if( LibertyMime::expunge() ) { + $ret = TRUE; + $this->CompleteTrans(); + } else { + $this->mDb->RollbackTrans(); + } + } + return $ret; + } + + //delete the comment and all of its children + //it should be possible to do this in a single query using the materialized path + //this is the code from the old function which needs to be revised + // 1) change name + // 2) use materialized path to cut query count and eliminate recursion + + function expunge() { + global $gBitSystem; + $ret = FALSE; + if( $this->isValid() ) { + $this->StartTrans(); + $sql = "SELECT `comment_id` FROM `".BIT_DB_PREFIX."liberty_comments` WHERE `parent_id` = ?"; + $rows = $this->mDb->getAll($sql, array($this->mContentId)); + + foreach ($rows as $row) { + $comment = new LibertyComment($row['comment_id']); + $comment->expunge(); + } + + if( $gBitSystem->isPackageActive( 'boards' ) ) { + // due to foreign key constraints, this has to go in the base class of BitBoardPost + $sql = "DELETE FROM `".BIT_DB_PREFIX."boards_posts` WHERE `comment_id` = ?"; + $rs = $this->mDb->query($sql, array($this->mCommentId ) ); + $query = "DELETE FROM `".BIT_DB_PREFIX."boards_topics` WHERE `parent_id` = ?"; + $result = $this->mDb->query( $query, array( $this->getField( 'content_id' ) ) ); + } + + $sql = "DELETE FROM `".BIT_DB_PREFIX."liberty_comments` WHERE `comment_id` = ?"; + $rs = $this->mDb->query($sql, array($this->mCommentId)); + + /* + * TODO: figureout why this is even here. Mime should handle this and it needs to pass in mandatory attachmentId + * Slated to delete for now - wjames5 + */ + /* + if (method_exists($this,'expungeMetaData')) { + $this->expungeMetaData(); + } + */ + + if( LibertyMime::expunge() ) { + $ret = TRUE; + $this->CompleteTrans(); + } else { + $this->mDb->RollbackTrans(); + } + } + return $ret; + } + + function userCanEdit() { + global $gBitUser, $gBitSystem; + $ret = FALSE; + + // check the allowed edit time limit - we'll use it later + $withinEditTime = FALSE; + if ( $gBitSystem->getConfig( 'comments_edit_minutes', 60 ) * 60 + $this->getField( 'created' ) > time() ) { + $withinEditTime = TRUE; + } + if( $gBitUser->isRegistered() ) { + /* get the hash of the users perms rather than call hasUserPermission which + * always returns true for owner which interferes with trying to time limit editing + */ + $checkPerms = $this->getUserPermissions(); + $ret = ( !empty( $checkPerms['p_liberty_edit_comments'] ) || + !empty( $checkPerms['p_liberty_admin_comments'] ) || + $gBitUser->hasPermission( 'p_liberty_admin_comments' ) || + ( $gBitUser->mUserId == $this->mInfo['user_id'] && $withinEditTime ) + ); + } elseif( $this->mInfo['user_id'] == ANONYMOUS_USER_ID ) { + $ret = (($_SERVER['REMOTE_ADDR']==$this->mInfo['ip']) && $withinEditTime ); + } + return $ret; + } + + function userCanUpdate( $pRootContent=NULL ) { + return( $this->userCanEdit() || ($pRootContent && ($pRootContent->hasUserPermission( 'p_liberty_edit_comments' ) || $pRootContent->hasUserPermission( 'p_liberty_admin_comments' ))) ); + } + + /** + * @param pLinkText name of + * @param pParamHash different possibilities depending on derived class + * @return the link to display the page. + */ + public static function getDisplayUrlFromHash( &$pParamHash ) { + $ret = NULL; + if( @BitBase::verifyId( $pParamHash['root_id'] ) && $viewContent = LibertyBase::getLibertyObject( $pParamHash['root_id'] ) ) { + // pass in cooment hash to the url func incase the root package needs to do something fancy + $viewContent->mInfo['comment'] = $pParamHash; + $ret = $viewContent->getDisplayUrl().( @static::verifyId( $pParamHash['content_id'] ) ? "#comment_".$pParamHash['content_id'] : '' ); + } elseif( @BitBase::verifyId( $pParamHash['content_id'] ) ) { + $ret = parent::getDisplayUrlFromHash( $pParamHash ); + $ret .= "#comment_{$pParamHash['content_id']}"; + } + + return( $ret ); + } + + //generate a URL to directly access and display a single comment and the associated root content + public static function getDirectUrlFromHash( $pParamHash=NULL ) { + if( empty( $pParamHash ) ) { + $pParamHash = &$this->mInfo; + } + $ret = NULL; + if( !empty( $pParamHash['root_id'] ) && $viewContent = LibertyBase::getLibertyObject( $pParamHash['root_id'] ) ) { + $ret = $viewContent->getDisplayUrl(); + if ( strstr($ret, '?') ) { + $ret .= "&"; + } + else { + $ret .= "?"; + } + $ret .= "view_comment_id=" . $pParamHash['content_id'] . "#comment_".$pParamHash['content_id']; + } + return ( $ret ); + } + + public static function getDisplayLinkFromHash( &$pParamHash, $pLinkText=NULL, $pAnchor=NULL ) { + $anchor = ''; + // Override default title with something comment centric + if( empty( $pLinkText ) ) { + $pLinkText = tra( 'Comment' ); + } + if( @BitBase::verifyId( $pParamHash['content_id'] )) { + $anchor = "&view_comment_id=".$pParamHash['content_id']."#comment_{$pParamHash['content_id']}"; + } + return parent::getDisplayLinkFromHash( $pParamHash, $pLinkText, $anchor ); + } + + function getList( &$pParamHash ) { + global $gBitSystem, $gLibertySystem; + if ( !isset( $pParamHash['sort_mode']) or $pParamHash['sort_mode'] == '' ){ + $pParamHash['sort_mode'] = 'created_desc'; + } + if( empty( $pParamHash['max_records'] ) ) { + $pParamHash['max_records'] = $gBitSystem->getConfig( 'max_records' ); + } + LibertyContent::prepGetList( $pParamHash ); + $sort_mode = $this->mDb->convertSortmode($pParamHash['sort_mode']); + + $joinSql = $whereSql = $selectSql = ''; + $bindVars = $ret = array(); + + $pParamHash['include_comments'] = TRUE; + $this->getServicesSql( 'content_list_sql_function', $selectSql, $joinSql, $whereSql, $bindVars, NULL, $pParamHash ); + + if ( !empty( $pParamHash['root_content_type_guid'] ) ) { + if( is_string( $pParamHash['root_content_type_guid'] ) ) { + $pParamHash['root_content_type_guid'] = array( $pParamHash['root_content_type_guid'] ); + } elseif( is_array( $pParamHash['root_content_type_guid'] ) ) { + $contentTypes = array_keys( $gLibertySystem->mContentTypes ); + $max = count( $pParamHash['root_content_type_guid'] ); + $guidSql = ''; + for( $i = 0; $i < $max; $i++ ) { + if( in_array( $pParamHash['root_content_type_guid'][$i], $contentTypes ) ) { + if( strlen( $guidSql ) ) { + $guidSql .= ' OR '; + } + $guidSql .= " rlc.`content_type_guid`=? "; + $bindVars[] = $pParamHash['root_content_type_guid'][$i]; + } + } + $whereSql .= " AND ( $guidSql )"; + } + } + + if ( !empty( $pParamHash['content_type_guid'] ) ) { + $whereSql .= " AND rlc.`content_type_guid`=? "; + $bindVars[] = $pParamHash['content_type_guid']; + } + + if ( !empty( $pParamHash['user_id'] ) ) { + $whereSql .= " AND ptc.`user_id`=? "; + $bindVars[] = $pParamHash['user_id']; + } + + if ( !empty( $pParamHash['created_ge'] ) ) { + $whereSql .= " AND lc.`created`>=? "; + $bindVars[] = $pParamHash['created_ge']; + } + + // left outer join on root so updater works + + $query = "SELECT + lcom.`comment_id`, + lc.`content_id`, + lcom.`parent_id`, + lcom.`anon_name`, + lcom.`root_id`, + lc.`title` AS `content_title`, + rlc.`title` AS `root_content_title`, + lc.`created`, + lc.`data`, + lc.`last_modified` as `last_modified`, + lc.`title` as `title`, + ptc.`content_type_guid` as `parent_content_type_guid`, + rlc.`content_type_guid` as `root_content_type_guid`, + lc.`content_type_guid`, + uu.`login` AS `creator_user`, + uu.`login`, + uu.`real_name`, + uu.`user_id` + $selectSql + FROM `".BIT_DB_PREFIX."liberty_comments` lcom + INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON( lcom.`content_id`=lc.`content_id` ) + INNER JOIN `".BIT_DB_PREFIX."users_users` uu ON( uu.`user_id`=lc.`user_id`) + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content` rlc ON( rlc.`content_id`=lcom.`root_id` ) + $joinSql, `".BIT_DB_PREFIX."liberty_content` ptc + WHERE lcom.`parent_id`=ptc.`content_id` $whereSql + ORDER BY $sort_mode"; + if( $result = $this->mDb->query( $query, $bindVars, $pParamHash['max_records'], $pParamHash['offset'] )) { + while( $row = $result->FetchRow() ) { + $row['display_link'] = $this->getDisplayLink( $row['content_title'], $row ); + $row['display_url'] = static::getDisplayUrlFromHash( $row ); + $row['direct_url'] = static::getDirectUrlFromHash( $row ); + if (!empty($pParamHash['parse'])) { + $row['parsed_data'] = self::parseDataHash( $row ); + } + $ret[] = $row; + } + } + + return $ret; + } + + /** + * Fill title with date if available + * + * This will normally be overwriten by extended classes to provide + * an appropriate title title string + * @param array mInfo type hash of data to be used to provide base data + * @return string Descriptive title for the object + */ + public static function getTitleFromHash( &$pHash, $pDefault=TRUE ) { + global $gBitSmarty; + $ret = NULL; + if( !empty( $pHash['title'] ) ) { + $ret = $pHash['title']; + } elseif( !empty( $pHash['created'] ) ) { + $gBitSmarty->loadPlugin( 'smarty_modifier_bit_short_date' ); + $ret = smarty_modifier_bit_short_date( $pHash['created'] ); + } elseif( !empty( $pHash['content_name'] ) ) { + $ret = $pHash['content_name']; + } + return $ret; + } + + + function getNumComments($pContentId = NULL) { + $bindVars = NULL; + if (!$pContentId && $this->mContentId) { + $mid = '=?'; + $bindVars = array($this->mContentId); + } elseif (is_array($pContentId)) { + $mid = 'in ('.implode(',', array_fill(0, count( $pContentId ), '?')).')'; + $bindVars = $pContentId; + } elseif ($pContentId) { + $mid = '=?'; + $bindVars = array($pContentId); + } + $commentCount = 0; + + $joinSql = $selectSql = $whereSql = ''; + + /* brute force call to liberty_content_list_sql + * for status enforcement + * + * here we call liberty_content_list_sql which has a + * restriction to enforce content_status_id. we could + * have called the full list_sql service, but that + * would be overkill for just getting a count. + */ + if ( !is_array($pContentId) ){ + $sqlHash = liberty_content_list_sql( $this, NULL ); + if( !empty( $sqlHash['select_sql'] ) ) { + $selectSql .= $sqlHash['select_sql']; + } + if( !empty( $sqlHash['join_sql'] ) ) { + $joinSql .= $sqlHash['join_sql']; + } + if( !empty( $sqlHash['where_sql'] ) ) { + $whereSql .= $sqlHash['where_sql']; + } + if( !empty( $sqlHash['bind_vars'] ) ) { + if ( is_array( $bindVars ) ) { + $bindVars = array_merge( $bindVars, $sqlHash['bind_vars'] ); + } else { + $bindVars = $sqlHash['bind_vars']; + } + } + } + + if ($bindVars) { + $sql = "SELECT count(*) as comment_count $selectSql + FROM `".BIT_DB_PREFIX."liberty_comments` lcom + INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON (lcom.`content_id` = lc.`content_id`) $joinSql + WHERE lcom.`root_id` $mid $whereSql"; + $commentCount = $this->mDb->getOne($sql, $bindVars); + } + return $commentCount; + } + + + // used for direct access to view a single comment + // see usage in: liberty/comments_inc.php + // there ought to be a better way to do this... + function getNumComments_upto($pCommentId = NULL, $pContentId = NULL) { + + $comment = new LibertyComment($pCommentId, $pContentId); + + #assume flat mode + $comment_fields = $comment->mInfo; + $created = $comment_fields['created']; + $contentId = $comment_fields['root_id']; + + $commentCount = 0; + if ($contentId) { + $sql = "SELECT count(*) + FROM `".BIT_DB_PREFIX."liberty_comments` tc LEFT OUTER JOIN + `".BIT_DB_PREFIX."liberty_content` tcn + ON (tc.`content_id` = tcn.`content_id`) + where tc.`root_id` =? and `created` < ?"; + $commentCount = $this->mDb->getOne($sql, array($contentId, $created)); + } + return $commentCount; + } + + // Returns a hash containing the comment tree of comments related to this content + function getComments( $pContentId = NULL, $pMaxComments = NULL, $pOffset = NULL, $pSortOrder = NULL, $pDisplayMode = NULL ) { + if( $pDisplayMode != "flat" ) { + if ($pSortOrder == "commentDate_asc") { + $pSortOrder = 'thread_asc'; + } else { + $pSortOrder = 'thread_desc'; + } + } + + $contentId = NULL; + $ret = array(); + if (!$pContentId && $this->mContentId) { + $contentId = $this->mContentId; + } elseif ($pContentId) { + $contentId = $pContentId; + } + + $mid = ""; + + $sort_order = "ASC"; + $mid = 'created ASC'; + if (!empty($pSortOrder)) { + if ($pSortOrder == 'commentDate_desc') { + $mid = 'created DESC'; + } else if ($pSortOrder == 'commentDate_asc') { + $mid = 'created ASC'; + } elseif ($pSortOrder == 'thread_asc') { + $mid = 'thread_forward_sequence ASC'; + // thread newest first is harder... + } elseif ($pSortOrder == 'thread_desc') { + $mid = 'thread_reverse_sequence ASC'; + } else { + $mid = $this->mDb->convertSortmode( $pSortOrder ); + } + } + $mid = 'order by ' . $mid; + + $bindVars = array(); + if (is_array( $pContentId ) ) { + $mid2 = 'in ('.implode(',', array_fill(0, count( $pContentId ), '?')).')'; + $bindVars = $pContentId; + $select1 = ', lcp.`content_type_guid` as parent_content_type_guid, lcp.`title` as parent_title '; + $join1 = " LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content` lcp ON (lcp.`content_id` = lcom.`parent_id`) "; + } elseif ($pContentId) { + $mid2 = '=?'; + $bindVars = array( $pContentId ); + $select1 = ''; + $join1 = ''; + } + + $joinSql = $selectSql = $whereSql = ''; + $pListHash = array( 'content_id' => $contentId, 'max_records' => $pMaxComments, 'offset'=>$pOffset, 'sort_mode'=> $pSortOrder, 'display_mode' => $pDisplayMode, 'has_comment_view_perm' => TRUE ); + $this->getServicesSql( 'content_list_sql_function', $selectSql, $joinSql, $whereSql, $bindVars, $this, $pListHash ); + + if ($pContentId) { + $sql = "SELECT lcom.`comment_id`, lcom.`parent_id`, lcom.`root_id`, lcom.`thread_forward_sequence`, lcom.`thread_reverse_sequence`, lcom.`anon_name`, lc.*, uu.`email`, uu.`real_name`, uu.`login` $selectSql $select1 + FROM `".BIT_DB_PREFIX."liberty_comments` lcom + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON (lcom.`content_id` = lc.`content_id`) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_users` uu ON (lc.`user_id` = uu.`user_id`) $joinSql $join1 + WHERE lcom.root_id $mid2 $whereSql $mid"; + $flat_comments = array(); + if( $result = $this->mDb->query( $sql, $bindVars, $pMaxComments, $pOffset ) ) { + while( $row = $result->FetchRow() ) { + $row['parsed_data'] = self::parseDataHash( $row ); + $row['level'] = substr_count ( $row['thread_forward_sequence'], '.' ) - 1; + $c = new LibertyComment(); + $c->mInfo=$row; + $c->mRootObj = $this->getRootObj(); + $row['is_editable'] = $c->userCanUpdate( $c->mRootObj ); + + global $gBitSystem; + if( $gBitSystem->isFeatureActive( 'comments_allow_attachments' ) ){ + // get attachments for each comment + global $gLibertySystem; + $query = "SELECT * FROM `".BIT_DB_PREFIX."liberty_attachments` la WHERE la.`content_id`=? ORDER BY la.`pos` ASC, la.`attachment_id` ASC"; + if( $result2 = $this->mDb->query( $query,array( (int)$row['content_id'] ))) { + while( $row2 = $result2->fetchRow() ) { + if( $func = $gLibertySystem->getPluginFunction( $row2['attachment_plugin_guid'], 'load_function', 'mime' )) { + // we will pass the preferences by reference that the plugin can easily update them + if( empty( $row['storage'][$row2['attachment_id']] )) { + $row['storage'][$row2['attachment_id']] = array(); + } + $row['storage'][$row2['attachment_id']] = $func( $row2, $row['storage'][$row2['attachment_id']] ); + } else { + print "No load_function for ".$row2['attachment_plugin_guid']; + } + } + } + // end get attachements for each comment + } + + $flat_comments[$row['content_id']] = $row; + } + } + + # now select comments wanted + $ret = $flat_comments; + + } + return $ret; + } + + function getQuoted() { + $data = $this->mInfo['data']; + $pattern = '/\{quote .*\}(.*)\{\/quote\}/i'; + $replacement = ''; + $data = preg_replace($pattern, $replacement, $data); + return '{quote format_guid="'.$this->mInfo['format_guid'].'" comment_id="'.$this->mCommentId.'" user="'.$this->mInfo['login'].'"}'.trim($data).'{/quote}'; + } + + // Basic formatting for quoting comments + function quoteComment($commentData) { + $ret = '> '.$commentData; + $ret = eregi_replace("\n", "\n>", $ret); + return $ret; + } + + function getRootObj(){ + if ( !is_object( $this->mRootObj ) && !empty( $this->mInfo['root_id'] ) ){ + if ( $obj = LibertyBase::getLibertyObject( $this->mInfo['root_id'] ) ) { + $this->mRootObj = $obj; + } + } + return $this->mRootObj; + } +} + +?> diff --git a/includes/classes/LibertyContent.php b/includes/classes/LibertyContent.php new file mode 100644 index 0000000..4da4f1b --- /dev/null +++ b/includes/classes/LibertyContent.php @@ -0,0 +1,3844 @@ +<?php +/** +/* Management of Liberty content +* +* @package liberty +* @author spider <spider@steelsun.com> +*/ + +// +----------------------------------------------------------------------+ +// | Copyright (c) 2004, bitweaver.org +// +----------------------------------------------------------------------+ +// | 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 +// | +// | For comments, please use phpdocu.sourceforge.net documentation standards!!! +// | -> see http://phpdocu.sourceforge.net/ +// +----------------------------------------------------------------------+ +// | Authors: spider <spider@steelsun.com> +// +----------------------------------------------------------------------+ + +/** + * Maximum lengths for database fields + */ +if( !defined( 'BIT_CONTENT_MAX_TITLE_LEN' ) ) { + define( 'BIT_CONTENT_MAX_TITLE_LEN', 160); +} +define( 'BIT_CONTENT_MAX_LANGUAGE_LEN', 4); +define( 'BIT_CONTENT_MAX_IP_LEN', 39); +define( 'BIT_CONTENT_MAX_FORMAT_GUID_LEN', 16); + +if( !defined( 'BIT_CONTENT_DEFAULT_STATUS' ) ) { + define( 'BIT_CONTENT_DEFAULT_STATUS', 50); +} +//$gBitSystem->getConfig( 'liberty_status_deleted', -999 ) ); +//$gBitSystem->getConfig( 'liberty_status_threshold_private', -40 ) ); +//$gBitSystem->getConfig( 'liberty_status_threshold_protected', -20 ) ); +//$gBitSystem->getConfig( 'liberty_status_threshold_hidden', -10 ) ); + +/** + * required setup + */ +require_once( LIBERTY_PKG_CLASS_PATH.'LibertyBase.php' ); + +define( 'LIBERTY_SPLIT_REGEX', "!\.{3}split\.{3}[\t ]*\n?!" ); + +/** + * Virtual base class (as much as one can have such things in PHP) for all + * derived tikiwiki classes that require database access. + * + * @package liberty + */ +class LibertyContent extends LibertyBase implements BitCacheable { + /** + * Content Id if an object has been loaded + * @public + */ + public $mContentId; + + /** + * If this content is being viewed within a structure + * @public + */ + public $mStructureId; + + /** + * Content type GUID for this LibertyContent object + * @public + */ + public $mContentTypeGuid; + + /** + * Content type hash for this LibertyContent object + * @public + */ + public $mType; + + /** + *Permissions hash specific to the user accessing this LibertyContent object + * @public + */ + public $mUserContentPerms; + + /** + * Preferences hash specific to this LibertyContent object - accessed via getPreference/storePreference + * @private + */ + public $mPrefs = NULL; + + /** + * Control permission specific to this LibertyContent type + * @private + */ + public $mViewContentPerm; + public $mUpdateContentPerm; + public $mCreateContentPerm; + public $mExpungeContentPerm; + public $mAdminContentPerm; + + /** + * Construct an empty LibertyBase object with a blank permissions array + */ + function __construct() { + parent::__construct(); + $this->mPrefs = NULL; // init to NULL so getPreference can determine if a load is necessary + + // NOTE: we are not assigning anything to mViewContentPerm. if this is empty, we will return TRUE in hasViewPermission() + if( empty( $this->mUpdateContentPerm )) { + $this->mUpdateContentPerm = 'p_admin_content'; + } + + if( empty( $this->mCreateContentPerm )) { + $this->mCreateContentPerm = 'p_admin_content'; + } + + if( empty( $this->mExpungeContentPerm )) { + $this->mExpungeContentPerm = 'p_admin_content'; + } + + if( empty( $this->mAdminContentPerm )) { + $this->mAdminContentPerm = 'p_admin_content'; + } + } + + public static function isCacheableClass() { + global $gBitSystem; + // new feature, cache by default in development systems only + return !$gBitSystem->isLive(); + } + + + public function isCacheableObject() { + return parent::isCacheableObject() && !empty( $this->mContentId ); + } + + public function getCacheKey() { + if( $this->isValid() ) { + return $this->mContentId; + } + } + + public function __sleep() { + return array_merge( parent::__sleep(), array( 'mContentId', 'mInfo', 'mStructureId', 'mContentTypeGuid', 'mType', 'mUserContentPerms', 'mPrefs', 'mViewContentPerm', 'mUpdateContentPerm', 'mCreateContentPerm', 'mExpungeContentPerm', 'mAdminContentPerm') ); + } + + /** + * load Assume a derived class has joined on the liberty_content table, and loaded it's columns already. + * + * @access public + * @return void + */ + public function load() { + if( !empty( $this->mInfo['content_type_guid'] )) { + global $gLibertySystem, $gBitSystem, $gBitUser; + $this->loadPreferences(); + $this->mInfo['content_type'] = $gLibertySystem->mContentTypes[$this->mInfo['content_type_guid']]; + $this->invokeServices( 'content_load_function', $this ); + } + } + + /** + * Verify the core class data required to update the liberty_content table entries + * + * Verify will build an array [content_store] with all of the required values + * and populate it with the relevent data to create/update the liberty_content + * table record + * + * @param array $pParamHash Array of content data to be stored + * + * @param array $pParamHash[content_id] + * @param array $pParamHash[user_id] + * @param array $pParamHash[modifier_user_id] + * @param array $pParamHash[created] + * @param array $pParamHash[last_modified] + * @param array $pParamHash[content_type_guid] + * @param array $pParamHash[format_guid] + * @param array $pParamHash[last_hit] + * @param array $pParamHash[event_time] + * @param array $pParamHash[hits] + * @param array $pParamHash[lang_code] + * @param array $pParamHash[title] + * @param array $pParamHash[ip] + * @param array $pParamHash[edit] + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function verify( &$pParamHash ) { + global $gLibertySystem, $gBitSystem, $gBitLanguage, $gBitUser; + + // It is possible a derived class set this to something different + if( empty( $pParamHash['content_type_guid'] ) ) { + $pParamHash['content_type_guid'] = $this->mContentTypeGuid; + } + + if( empty( $pParamHash['user_id'] ) ) { + $pParamHash['user_id'] = $gBitUser->getUserId(); + } + + if( $this->verifyIdParameter( $pParamHash, 'content_id' ) && $pParamHash['content_id'] != $this->mContentId ) { + // we have request for a content, but is not the same as this object, something stinky going on. + // Unset the pParamHash['content_id'] and let mContentId be used going forward + unset( $pParamHash['content_id'] ); + } + + if( !$this->verifyIdParameter( $pParamHash, 'content_id' ) ) { + if( !$this->verifyId( $this->mContentId ) ) { + // These should never be updated, only inserted + $pParamHash['content_store']['created'] = !empty( $pParamHash['created'] ) ? $pParamHash['created'] : $gBitSystem->getUTCTime(); + // This may get overridden by owner set + $pParamHash['content_store']['user_id'] = $pParamHash['user_id']; + // Set a default status when creating if none is set + // This may get overwritten below + if( empty($pParamHash['content_store']['content_status_id'] ) ){ + $pParamHash['content_store']['content_status_id'] = $gBitSystem->getConfig('liberty_default_status', BIT_CONTENT_DEFAULT_STATUS); + } + } else { + $pParamHash['content_id'] = $this->mContentId; + } + } + + if( BitBase::verifyIdParameter( $pParamHash, 'content_id' ) ) { + $pParamHash['content_store']['content_id'] = $pParamHash['content_id']; + } + + // Are we allowed to override owner? + if( !empty($pParamHash['owner_id'] ) ) { + if( $gBitUser->isAdmin() || ($gBitSystem->isFeatureActive('liberty_allow_change_owner') && $gBitUser->hasPermission('p_liberty_edit_content_owner') && !empty($pParamHash['owner_id']) && !empty($pParamHash['current_owner_id']) && $pParamHash['owner_id'] != $pParamHash['current_owner_id']) ) { + // If an owner is being set override user_id + $pParamHash['content_store']['user_id'] = $pParamHash['owner_id']; + } + } + + // Do we need to change the status + if (!empty($pParamHash['content_status_id'])) { + if( $this->hasUserPermission( 'p_liberty_edit_content_status' ) || $gBitUser->hasUserPermission( 'p_liberty_edit_all_status') ) { + $allStatus = $this->getAvailableContentStatuses(); + if (empty($allStatus[$pParamHash['content_status_id']])) { + $this->mErrors['content_status_id'] = "No such status ID or permission denied."; + } else { + $pParamHash['content_store']['content_status_id'] = $pParamHash['content_status_id']; + } + } + } + + $pParamHash['field_changed'] = empty( $pParamHash['content_id'] ) + || (!empty($this->mInfo["data"]) && !empty($pParamHash["edit"]) && (md5($this->mInfo["data"]) != md5($pParamHash["edit"]))) + || (!empty($pParamHash["title"]) && !empty($this->mInfo["title"]) && (md5($this->mInfo["title"]) != md5($pParamHash["title"]))) + || (!empty($pParamHash["edit_comment"]) && !empty($this->mInfo["edit_comment"]) && (md5($this->mInfo["edit_comment"]) != md5($pParamHash["edit_comment"]))); + // check some lengths, if too long, then truncate + if( !empty( $pParamHash['title'] ) ) { + $pParamHash['content_store']['title'] = substr( preg_replace( '/:space:+/m', ' ', trim( $pParamHash['title'] ) ), 0, BIT_CONTENT_MAX_TITLE_LEN ); + } elseif( isset( $pParamHash['title'] ) ) { + $pParamHash['content_store']['title'] = NULL; + } + + // get the lang code from $_REQUEST if it's not set + if( !empty( $pParamHash['lang_code'] ) && in_array( $pParamHash['lang_code'], array_keys( $gBitLanguage->mLanguageList ) ) ) { + $pParamHash['content_store']['lang_code'] = $pParamHash['lang_code']; + } elseif( !empty( $_REQUEST['i18n']['lang_code'] ) && in_array( $_REQUEST['i18n']['lang_code'], array_keys( $gBitLanguage->mLanguageList ) ) ) { + $pParamHash['content_store']['lang_code'] = $_REQUEST['i18n']['lang_code']; + } + + $pParamHash['content_store']['last_modified'] = !empty( $pParamHash['last_modified'] ) ? $pParamHash['last_modified'] : $gBitSystem->getUTCTime(); + if( !empty( $pParamHash['event_time'] ) ) { + $pParamHash['content_store']['event_time'] = $pParamHash['event_time']; + } + + // WARNING: Assume WIKI if t + if( !empty( $pParamHash['content_id'] ) ) { + // do NOT allow changing of content_type_guid in update for safety of overridden secondary classes (like BitBook ) + unset( $pParamHash['content_store']['content_type_guid'] ); + } elseif( empty( $pParamHash['content_type_guid'] ) ) { + $this->mErrors['content_type'] = tra( 'System Error: Unknown content type' ); + } else { + $pParamHash['content_store']['content_type_guid'] = $pParamHash['content_type_guid']; + } + + // setup some required defaults if not defined + if( empty( $pParamHash['ip'] ) ) { + if( empty( $_SERVER["REMOTE_ADDR"] ) ) { + $pParamHash['ip'] = '127.0.0.1'; + } else { + $pParamHash['ip'] = $_SERVER["REMOTE_ADDR"]; + } + } + $pParamHash['content_store']['ip'] = $pParamHash['ip']; + + if( !@$this->verifyId( $pParamHash['modifier_user_id'] ) ) { + $pParamHash['modifier_user_id'] = $gBitUser->getUserId(); + } + $pParamHash['content_store']['modifier_user_id'] = $pParamHash['modifier_user_id']; + + if( empty( $pParamHash['format_guid'] ) ) { + $pParamHash['format_guid'] = $gBitSystem->getConfig( 'default_format', 'tikiwiki' ); + } + $pParamHash['content_store']['format_guid'] = $pParamHash['format_guid']; + + if( !empty( $pParamHash['hits'] ) ) { + $pParamHash['content_store']['hits'] = $pParamHash['hits'] + 1; + $pParamHash['content_store']['last_hit'] = $gBitSystem->getUTCTime(); + } + + if( !empty( $pParamHash['edit'] ) && $func = $gLibertySystem->getPluginFunction( $pParamHash['format_guid'], 'verify_function' ) ) { + $error = $func( $pParamHash ); + if( $error ) { + $this->mErrors['format'] = $error; + } + } + + if( !empty( $pParamHash['content_store']['data'] )) { + $this->filterData( $pParamHash['content_store']['data'], $pParamHash['content_store'], 'prestore' ); + } else { + // someone has deleted the data entirely - common for fisheye + $pParamHash['content_store']['data'] = NULL; + } + $pParamHash['content_store']['format_guid'] = $pParamHash['format_guid']; + + if( !@BitBase::verifyId( $this->mInfo['version'] ) ) { + $pParamHash['content_store']['version'] = 1; + } else { + $pParamHash['content_store']['version'] = $this->mInfo['version'] + 1; + } + + // search related stuff + if ( ( !(isset($this->mInfo['no_index']) and $this->mInfo['no_index'] == true ) ) and !isset($this->mInfo['index_data']) ) { + $this->mInfo['index_data'] = ""; + if ( isset($pParamHash["title"]) ) $this->mInfo['index_data'] .= $pParamHash["title"] . ' '; + if ( isset($pParamHash["author_name"]) ) $this->mInfo['index_data'] .= $pParamHash["author_name"] . ' '; + if ( isset($pParamHash["edit"]) ) $this->mInfo['index_data'] .= $pParamHash["edit"]; + } + + // content preferences + $prefs = array(); + 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]; + } else { + $pParamHash['preferences_store'][$pref] = NULL; + } + } + $pParamHash['data_store']['summary'] = !empty( $pParamHash['summary'] ) ? $pParamHash['summary'] : NULL ; + + // call verify service to see if any services have errors + $this->invokeServices( 'content_verify_function', $pParamHash ); + + return( count( $this->mErrors ) == 0 ); + } + + /** + * Create a new content object or update an existing one + * + * @param array Array of content data to be stored <br> + * See verify for details of the values required + */ + function store( &$pParamHash ) { + global $gLibertySystem; + if( LibertyContent::verify( $pParamHash ) ) { + $this->clearFromCache(); + $this->StartTrans(); + $table = BIT_DB_PREFIX."liberty_content"; + if( !$this->verifyIdParameter( $pParamHash, 'content_id' ) ) { + // make sure some variables are stuff in case services need getObjectType, mContentId, etc... + $this->mContentId = $pParamHash['content_id'] = $pParamHash['content_store']['content_id'] = $this->mDb->GenID( 'liberty_content_id_seq' ); + $this->mContentTypeGuid = $this->mInfo['content_type_guid'] = $pParamHash['content_type_guid']; + $result = $this->mDb->associateInsert( $table, $pParamHash['content_store'] ); + $this->mLogs['content_store'] = "Created"; + } else { + if( !empty( $pParamHash['content_store']['title'] ) && !empty( $this->mInfo['title'] ) && $pParamHash['content_store']['title'] != $this->mInfo['title'] ) { + $this->mLogs['rename_page'] = "Renamed from {$this->mInfo['title']} to {$pParamHash['content_store']['title']}."; + } + $result = $this->mDb->associateUpdate( $table, $pParamHash['content_store'], array("content_id" => $pParamHash['content_id'] ) ); + $this->mLogs['content_store'] = "Updated"; + } + + if( !empty( $pParamHash['force_history'] ) || ( empty( $pParamHash['minor'] ) && $this->getField( 'version' ) && $pParamHash['field_changed'] )) { + if( empty( $pParamHash['has_no_history'] ) ) { + $this->storeHistory(); + } + //$action = "Created"; + //$mailEvents = 'wiki_page_changes'; + } + + $this->storeAliases( $pParamHash ); + + $this->invokeServices( 'content_store_function', $pParamHash ); + + // Call the formatter's save + if( !empty( $pParamHash['content_store']['data'] )) { + if( $func = $gLibertySystem->getPluginFunction( $pParamHash['format_guid'], 'store_function' ) ) { + $ret = $func( $pParamHash ); + } + + // post store filter - this is needed to deal with filters that need the content_id on the first save + $this->filterData( $pParamHash['content_store']['data'], $pParamHash['content_store'], 'poststore' ); + } + LibertyContent::expungeCacheFile( $pParamHash['content_id'] ); + + // store data + foreach( $pParamHash['data_store'] AS $dataType => $data ) { + $this->storeData( $data, $dataType ); + } + + // store content preferences + if( @is_array( $pParamHash['preferences_store'] ) ) { + foreach( $pParamHash['preferences_store'] as $pref => $value ) { + $this->storePreference( $pref, $value ); + } + } + + // store hits and last hit + if( !empty( $pParamHash['content_store']['hits'] ) ) { + $this->setHits($pParamHash['content_store']['hits'], $pParamHash['content_store']['last_hit']); + } + // store any messages in the logs + $this->storeActionLog( $pParamHash ); + + + $this->CompleteTrans(); + } + return( count( $this->mErrors ) == 0 ); + } + + /** + * Delete comment entries relating to the content object + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function expungeComments() { + require_once( LIBERTY_PKG_CLASS_PATH.'LibertyComment.php' ); + // Delete all comments associated with this piece of content + $query = "SELECT `comment_id` FROM `".BIT_DB_PREFIX."liberty_comments` WHERE `root_id` = ?"; + if( $commentIds = $this->mDb->getCol($query, array( $this->mContentId ) ) ) { + foreach ($commentIds as $commentId) { + $tmpComment = new LibertyComment($commentId); + $tmpComment->expunge(); + } + } + return parent::expunge(); + } + + /** + * Delete content object and all related records + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function expunge() { + global $gBitSystem, $gLibertySystem; + $ret = FALSE; + if( $this->isValid() ) { + $this->StartTrans(); + $this->expungeComments(); + + // services, filters and cache + $this->invokeServices( 'content_expunge_function', $this ); + if( $this->getField( 'format_guid' ) && $func = $gLibertySystem->getPluginFunction( $this->getField( 'format_guid' ), 'expunge_function' ) ) { + $func( $this->mContentId ); + } + $this->filterData( $this->mInfo['data'], $this->mInfo, 'expunge' ); + LibertyContent::expungeCacheFile( $this->mContentId ); + + // remove favorites - this probably should be a content_expunge_function in users + $this->mDb->query( "DELETE FROM `".BIT_DB_PREFIX."users_favorites_map` WHERE `favorite_content_id`=?", array( $this->mContentId ) ); + + // remove entries in the history + $this->expungeVersion(); + + // Remove individual permissions for this object if they exist + $query = "delete from `".BIT_DB_PREFIX."liberty_content_permissions` where `content_id`=?"; + $result = $this->mDb->query( $query, array( $this->mContentId ) ); + + // Remove aliases + $this->mDb->query( "DELETE FROM `".BIT_DB_PREFIX."liberty_aliases` WHERE `content_id`=?", array( $this->mContentId ) ); + + // Remove structures + // it's not this simple. what about orphans? needs real work. :( xoxo - spider +// $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_structures` WHERE `content_id` = ?"; +// $result = $this->mDb->query( $query, array( $this->mContentId ) ); + + // Remove any queued data processing (images, movies, etc.) + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_process_queue` WHERE `content_id` = ?"; + $result = $this->mDb->query( $query, array( $this->mContentId ) ); + + // Remove data + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_content_data` WHERE `content_id` = ?"; + $result = $this->mDb->query( $query, array( $this->mContentId ) ); + + // Remove hits + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_content_hits` WHERE `content_id` = ?"; + $result = $this->mDb->query( $query, array( $this->mContentId ) ); + + // Remove content preferences + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_content_prefs` WHERE `content_id` = ?"; + $result = $this->mDb->query( $query, array( $this->mContentId ) ); + + // Remove content links + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_content_links` WHERE `to_content_id` = ? or `from_content_id` = ?"; + $result = $this->mDb->query( $query, array( $this->mContentId, $this->mContentId ) ); + + // Remove content + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_content` WHERE `content_id` = ?"; + $result = $this->mDb->query( $query, array( $this->mContentId ) ); + + $this->mLogs['content_expunge'] = "Deleted"; + $this->storeActionLog(); + + $this->CompleteTrans(); + $ret = TRUE; + + parent::expunge(); + } + return $ret; + } + + /** + * storeAliases will store aliases to a given content item + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function storeAliases( $pParamHash ) { + $ret = FALSE; + if( $this->isValid() && isset( $pParamHash['alias_string']) ) { + $this->mDb->query( "DELETE FROM `".BIT_DB_PREFIX."liberty_aliases` WHERE `content_id`=?", array( $this->mContentId ) ); + $trimmedAliases = trim( $pParamHash['alias_string'] ); + if( !empty( $trimmedAliases ) && $aliases = explode( "\n", $trimmedAliases ) ) { + foreach( $aliases as $a ) { + $this->mDb->query( "INSERT INTO `".BIT_DB_PREFIX."liberty_aliases` (`content_id`, `alias_title`) VALUES (?,?)", array( $this->mContentId, trim( $a ) ) ); + } + } + $ret = TRUE; + } + return $ret; + } + + /** + * storeHistory will store the previous data into the history table for reference + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function storeHistory() { + global $gBitSystem; + + $ret = FALSE; + if( $this->isValid() ) { + $storeHash = array( + "content_id" => $this->mContentId, + "version" => $this->getField( "version" ), + "last_modified" => $this->getField( "last_modified" ), + "user_id" => $this->getField( "modifier_user_id" ), + "ip" => $this->getField( "ip" ), + "data" => $this->getField( "data" ), + "summary" => $this->getField( "summary" ), + "history_comment" => (string)substr( $this->getField( "edit_comment" ), 0, 200 ), + "format_guid" => $this->getField( "format_guid", $gBitSystem->getConfig( "default_format", "tikiwiki" )), + ); + $this->mDb->associateInsert( BIT_DB_PREFIX."liberty_content_history", $storeHash ); + $ret = TRUE; + } + return( $ret ); + } + + /** + * Get count of the number of historic records for the page + * + * @access public + * @return count + */ + function getHistoryCount() { + $ret = NULL; + if( $this->isValid() ) { + $query = " + SELECT COUNT(*) AS `hcount` + FROM `".BIT_DB_PREFIX."liberty_content_history` + WHERE `content_id` = ?"; + $rs = $this->mDb->query($query, array($this->mContentId)); + $ret = $rs->fields['hcount']; + } + return $ret; + } + + /** + * Get complete set of historical data in order to display a given wiki page version + * + * @param array $pVersion + * @param array $pUserId + * @param int $pOffset + * @param array $max_records + * @access public + * @return array of mInfo data + */ + function getHistory( $pVersion=NULL, $pUserId=NULL, $pOffset = 0, $max_records = -1 ) { + $ret = NULL; + $cant = 0; + if( $this->isValid() ) { + global $gBitSystem; + + $selectSql = ''; + $joinSql = ''; + $whereSql = ''; + $bindVars = array(); + $this->getServicesSql( 'content_list_history_sql_function', $selectSql, $joinSql, $whereSql, $bindVars ); + + $versionSql = ''; + if( @BitBase::verifyId( $pUserId ) ) { + $bindVars[] = $pUserId; + $whereSql .= ' th.`user_id`=? '; + } else { + $bindVars[] = $this->mContentId; + $whereSql .= ' th.`content_id`=? '; + } + if( BitBase::verifyId( $pVersion ) ) { + array_push( $bindVars, $pVersion ); + $versionSql = ' AND th.`version`=? '; + } + + $query = "SELECT COUNT(*) AS `hcount` + FROM `".BIT_DB_PREFIX."liberty_content_history` + WHERE `content_id` = ?"; + $rs = $this->mDb->query($query, array($this->mContentId)); + $cant = $rs->fields['hcount']; + + # Check for offset out of range + if ( $pOffset < 0 ) { + $pOffset = 0; + } elseif ( $pOffset > $cant ) { + $lastPageNumber = ceil ( $cant / $max_records ) - 1; + $pOffset = $max_records * $lastPageNumber; + } + + $query = "SELECT lc.`title`, th.*, + uue.`login` AS modifier_user, uue.`real_name` AS modifier_real_name, + uuc.`login` AS creator_user, uuc.`real_name` AS creator_real_name + $selectSql + FROM `".BIT_DB_PREFIX."liberty_content_history` th INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON (lc.`content_id` = th.`content_id`) + LEFT JOIN `".BIT_DB_PREFIX."users_users` uue ON (uue.`user_id` = th.`user_id`) + LEFT JOIN `".BIT_DB_PREFIX."users_users` uuc ON (uuc.`user_id` = lc.`user_id`) + $joinSql + WHERE $whereSql $versionSql order by th.`version` desc"; + + $result = $this->mDb->query( $query, $bindVars, $max_records, $pOffset ); + $data = array(); + while( !$result->EOF ) { + $aux = $result->fields; + $aux['creator'] = (isset( $aux['creator_real_name'] ) ? $aux['creator_real_name'] : $aux['creator_user'] ); + $aux['editor'] = (isset( $aux['modifier_real_name'] ) ? $aux['modifier_real_name'] : $aux['modifier_user'] ); + $data[] = $aux; + //array_push( $ret, $aux ); + $result->MoveNext(); + } + } + // Temporary patch to get a $pListHash array for the output + // this needs to be tidied on the input side + // TODO: update this to work like newer getList methods + $pListHash = array(); + $pListHash["data"] = $data; + $pListHash["cant"] = $cant; + $pListHash["max_records"] = $max_records; + $pListHash["offset"] = $pOffset; + $pListHash["find"] = NULL; + $pListHash["sort_mode"] = NULL; + LibertyContent::postGetList( $pListHash ); + return $pListHash; + } + + /** + * Removes last version of the page (from pages) if theres some + * version in the liberty_content_history then the last version becomes the actual version + * + * @param string $pComment + * @access public + * @return void + */ + function removeLastVersion( $pComment = '' ) { + if( $this->isValid() ) { + global $gBitSystem; + $this->expungeCacheFile($this->mContentId); + $query = "select * from `".BIT_DB_PREFIX."liberty_content_history` where `content_id`=? order by ".$this->convertSortMode("last_modified_desc"); + $result = $this->mDb->query($query, array( $this->mContentId ) ); + if ($result->numRows()) { + // We have a version + $res = $result->fetchRow(); + $this->rollbackVersion( $res["version"] ); + $this->expungeVersion( $res["version"] ); + } else { + $this->remove_all_versions($page); + } + $action = "Removed last version"; + $t = $gBitSystem->getUTCTime(); + $query = "insert into `".BIT_DB_PREFIX."liberty_action_log`( `log_message`, `content_id`, `last_modified`, `user_id`, `ip`, `error_message`) values( ?, ?, ?, ?, ?, ?)"; + $result = $this->mDb->query( $query, array( $action, $this->mContentId, $t, ROOT_USER_ID, $_SERVER["REMOTE_ADDR"], $pComment )); + } + } + + /** + * Roll back to a specific version of a page + * @param pVersion Version number to roll back to + * @param pComment Comment text to be added to the action log + * @return TRUE if completed successfully + */ + function rollbackVersion( $pVersion, $pComment = '' ) { + $ret = FALSE; + if( $this->isValid() ) { + global $gBitUser,$gBitSystem; + $this->StartTrans(); + // JHT - cache invalidation appears to be handled by store function - so don't need to do it here + $query = "select lch.*, lch.`user_id` AS modifier_user_id, lch.`data` AS `edit` from `".BIT_DB_PREFIX."liberty_content_history` lch where lch.`content_id`=? and lch.`version`=?"; + if( $res = $this->mDb->getRow($query,array( $this->mContentId, $pVersion ) ) ) { + $res['edit_comment'] = 'Rollback to version '.$pVersion.' by '.$gBitUser->getDisplayName(); + if (!empty($pComment)) { + $res['edit_comment'] .=": $pComment"; + } + // JHT 2005-06-19_15:22:18 + // set ['force_history'] to + // make sure we don't destory current content without leaving a copy in history + // if rollback can destroy the current page version, it can be used + // maliciously + $res['force_history'] = 1; + // JHT 2005-10-16_22:21:10 + // title must be set or store fails + // we use current page name + $res['title'] = $this->getTitle(); + if( $this->store( $res ) ) { + $ret = TRUE; + } + $this->CompleteTrans(); + } else { + $this->mDb->RollbackTrans(); + } + } + return $ret; + } + + /** + * Removes a specific version of a page + * + * @param pVersion Version number to roll back to + * @param pComment Comment text to be added to the action log + * @return TRUE if completed successfully + */ + function expungeVersion( $pVersion=NULL, $pComment = '' ) { + global $gBitUser; + $ret = FALSE; + if( $this->isValid() ) { + $this->StartTrans(); + $bindVars = array( $this->mContentId ); + $versionSql = ''; + if( $pVersion ) { + $versionSql = " and `version`=? "; + array_push( $bindVars, $pVersion ); + } + $hasRows = $this->mDb->getOne( "SELECT COUNT(`version`) FROM `".BIT_DB_PREFIX."liberty_content_history` WHERE `content_id`=? $versionSql ", $bindVars ); + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_content_history` WHERE `content_id`=? $versionSql "; + $result = $this->mDb->query( $query, $bindVars ); + if( $hasRows ) { + global $gBitSystem; + $action = "Removed version $pVersion"; + $t = $gBitSystem->getUTCTime(); + $query = "INSERT INTO `".BIT_DB_PREFIX."liberty_action_log` (`log_message`,`content_id`,`last_modified`,`user_id`,`ip`,`error_message`) VALUES (?,?,?,?,?,?)"; + $result = $this->mDb->query($query,array($action,$this->mContentId,$t,$gBitUser->mUserId,$_SERVER["REMOTE_ADDR"],$pComment)); + $ret = TRUE; + } + $this->CompleteTrans(); + } + return $ret; + } + + function exportList( $pList ) { + foreach( $pList as $keyId=>$hash ) { + $content = static::getLibertyObject( $keyId ); + $ret[$keyId] = $content->exportHash(); + } + return $ret; + } + + /** + * Create an export hash from the data + * + * @access public + * @return export data + */ + function exportHash() { + $ret = array(); + if( $this->isValid() ) { + $ret = array( + 'content_type_guid' => $this->getContentType(), + 'content_type' => $this->getContentTypeName(), + 'content_id' => $this->mContentId, + 'title' => $this->getTitle(), + 'display_uri' => $this->getDisplayUri(), + 'display_url' => $this->getDisplayUrl(), + 'date_created' => date( DateTime::W3C, $this->getField('created') ), + 'date_last_modified' => date( DateTime::W3C, $this->getField('last_modified') ), + ); + $keys = $this->invokeServices( 'content_export_keys_function', $pList ); + foreach( $keys as $field ) { + if( $value = $this->getField( $field ) ) { + $ret[$field] = $value; + } + } + } + return $ret; + } + + /** + * Check mContentId to establish if the object has been loaded with a valid record + */ + function isValid() { + return( BitBase::verifyId( $this->mContentId ) ); + } + + /** + * Check permissions to establish if user has permission to view the object + * Should be provided by the decendent package + */ + function isViewable($pContentId = NULL) { + return( true ); + } + + /** + * Check permissions to establish if user has permission to edit the object + * Should be provided by the decendent package + */ + function isEditable() { + return( false ); + } + + /** + * Check permissions to establish if user has permission to admin the object + * That would include permission to delete an object or change it's permissions + * Should be provided by the decendent package + */ + function isAdminable($pContentId = NULL) { + return( false ); + } + + /** + * Check user_id to establish if the object that has been loaded was created by the current user + * @param $pParamHash optionally pass in the hash to check against + * @return TRUE if user owns the content + */ + function isOwner( $pParamHash = NULL ) { + global $gBitUser; + if( @BitBase::verifyId( $pParamHash['user_id'] ) ) { + $user_id = $pParamHash['user_id']; + } elseif( $this->isValid() && @$this->verifyId( $this->mInfo['user_id'] ) ) { + $user_id = $this->mInfo['user_id']; + } + return( @BitBase::verifyId( $user_id ) && $user_id != ANONYMOUS_USER_ID && $user_id == $gBitUser->mUserId ); + } + + /** + * Check if content matches content type GUID - must also be a valid content object, it will not work for generic content class + */ + function isContentType( $pContentGuid ) { + global $gBitUser; + return( $this->isValid() && !empty( $this->mInfo['content_type_guid'] ) && $this->mInfo['content_type_guid'] == $pContentGuid ); + } + + /** + * Check permissions to establish if user has permission to access the object + */ + function verifyAccessControl() { + if( $this->isValid() ) { + $this->invokeServices( 'content_verify_access' ); + } + } + + /** + * Set up access to services used by the object + */ + function invokeServices( $pServiceFunction, &$pFunctionParam=NULL ) { + global $gLibertySystem; + $errors = array(); + // Invoke any services store functions such as categorization or access control + if( $serviceFunctions = $gLibertySystem->getServiceValues( $pServiceFunction ) ) { + foreach ( $serviceFunctions as $func ) { + if( function_exists( $func ) ) { + if( $errors = $func( $this, $pFunctionParam ) ) { + $this->mErrors = array_merge( $this->mErrors, $errors ); + } + } + } + } + return $errors; + } + + /** + * check if a service is active for this content type + * requires package LCConfig + * provisional method until LCConfig package is integrated into the core + */ + function hasService( $pServiceGuid ){ + global $gBitSystem; + $ret = TRUE; // we return true by default to preserve legacy service opperation which has no content type preferences + + if( $gBitSystem->isPackageActive( 'lcconfig' ) ){ + // LCConfig is a singleton class + $LCConfig = LCConfig::getInstance(); + // LCConfig negates services by content type + // if result is not 'n' then service should apply to this content type + if( $LCConfig->getConfig( 'service_'.$pServiceGuid, $this->mContentTypeGuid ) == 'n' ){ + $ret = FALSE; + } + } + + return $ret; + } + + + /** + * check if a service is required for this content type + * requires package LCConfig + * provisional method until LCConfig package is integrated into the core + */ + function isServiceRequired( $pServiceGuid ){ + global $gBitSystem; + $ret = TRUE; // we return true by default to preserve legacy service opperation which has no content type preferences + + if( $gBitSystem->isPackageActive( 'lcconfig' ) ){ + // LCConfig is a singleton class + $LCConfig = LCConfig::getInstance(); + return ( $LCConfig->getConfig( 'service_'.$pServiceGuid, $this->mContentTypeGuid ) == 'required' ); + } + + return $ret; + } + + + /** + * Default liberty sql for joining a content object table to liberty. + * We are proposing a new way of building queries here where we build up everything in a hash with implicit AND over all + * where clauses and then do an array_merge and concatenation in a single function at the end. See convertQueryHash for details. + * + * This is an example current, and would be invoked in getList + * $queryHash = array('summary', 'users', 'hits', 'avatar', 'primary'), array('select' => array('sql' => $selectSql), 'join' => array('sql' => $joinSql), 'where' => array('sql' => $whereSql, 'var' => $bindVars )); + * $this->getLibertySql( 'bp.`content_id`', $queryHash); + */ + function getLibertySql( $pJoinColumn, &$pQueryHash, $pJoins = NULL, $pServiceFunction = NULL, $pObject = NULL, $pParamHash = NULL ) { + $pQueryHash['select']['sql'][] = "lc.*"; + $pQueryHash['join']['sql'][] = " + INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON( lc.`content_id` = $pJoinColumn )"; + if( empty( $pJoins ) || in_array( 'summary', $pJoins )) { + $pQueryHash['select']['sql'][] = "lcds.`data` AS `summary`"; + $pQueryHash['join']['sql'][] = " + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_data` lcds ON( lc.`content_id` = lcds.`content_id` AND lcds.`data_type` = ? )"; + $pQueryHash['join']['var'][] = 'summary'; + } + if( empty( $pJoins ) || in_array( 'hits', $pJoins )) { + $pQueryHash['select']['sql'][] = "lch.`hits`, lch.`last_hit`"; + $pQueryHash['join']['sql'][] = " + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_hits` lch ON( lc.`content_id` = lch.`content_id` )"; + } + if( empty( $pJoins ) || in_array( 'users', $pJoins )) { + $pQueryHash['select']['sql'][] = " + uu.`email` AS creator_email, uu.`login` AS creator_user, uu.`real_name` AS creator_real_name, + uue.`email` AS modifier_email, uue.`login` AS modifier_user, uue.`real_name` AS modifier_real_name"; + $pQueryHash['join']['sql'][] = " + INNER JOIN `".BIT_DB_PREFIX."users_users` uu ON( uu.`user_id` = lc.`user_id` ) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_users` uue ON( uue.`user_id` = lc.`modifier_user_id` )"; + } + if( empty( $pJoins ) || in_array( 'avatar', $pJoins )) { + $pQueryHash['select']['sql'][] = "ulf.`file_name` AS `avatar_file_name`, ulf.`mime_type` AS `avatar_mime_type`, ula.`attachment_id` AS `avatar_attachment_id`"; + $pQueryHash['join']['sql'][] = " + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_attachments` ula ON( uu.`user_id` = ula.`user_id` AND ula.`attachment_id` = uu.`avatar_attachment_id` ) + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_files` ulf ON( ulf.`file_id` = ula.`foreign_id` )"; + } + if( empty( $pJoins ) || in_array( 'primary', $pJoins )) { + $pQueryHash['select']['sql'][] = "pla.`attachment_id` AS `primary_attachment_id`, plf.`file_name` AS `primary_file_name`, plf.`mime_type` AS `primary_mime_type`"; + $pQueryHash['join']['sql'][] = " + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_attachments` pla ON( pla.`content_id` = lc.`content_id` AND pla.`is_primary` = 'y' ) + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_files` plf ON( plf.`file_id` = pla.`foreign_id` )"; + } + + if( !empty( $pServiceFunction )) { + $this->getServicesSql2( $pServiceFunction, $pQueryHash, $pObject, $pParamHash ); + } + } + + /** + * getServicesSql2 + * + * @param array $pServiceFunction + * @param array $pQueryHash + * @param array $pObject + * @param array $pParamHash + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + * @TODO this function still contains legacy code. + * @TODO rename this function to getServicesSql has been weened out + */ + function getServicesSql2( $pServiceFunction, &$pQueryHash, $pObject = NULL, $pParamHash = NULL ) { + global $gLibertySystem; + if( $loadFuncs = $gLibertySystem->getServiceValues( $pServiceFunction ) ) { + // TODO: clear out this legacy code + $pQueryHash['service_select_sql'] = $pQueryHash['service_join_sql'] = $pQueryHash['service_where_sql'] = ''; + foreach( $loadFuncs as $func ) { + if( function_exists( $func ) ) { + if( !empty( $pObject ) && is_object( $pObject )) { + $queryHash = $func( $pObject, $pParamHash ); + } else { + $queryHash = $func( $this, $pParamHash ); + } + + // work out if we're using the old services sql method or the new one + if( !empty( $queryHash['select'] ) || !empty( $queryHash['from'] ) || !empty( $queryHash['join'] ) || !empty( $queryHash['where'] )) { + // we're using the new method + $pQueryHash = array_merge_recursive( $pQueryHash, $queryHash ); + } else { + // TODO: clean out this legacy code {{{ + // old method: warn the developer + //deprecated( 'This service is still using the old LibertyContent::getServicesSql() method. Please update the service to use the new SQL hash method' ); + if( !empty( $queryHash['select_sql'] )) { + $pQueryHash['service_select_sql'] .= $queryHash['select_sql']; + } + if( !empty( $queryHash['join_sql'] )) { + $pQueryHash['service_join_sql'] .= $queryHash['join_sql']; + } + if( !empty( $queryHash['where_sql'] )) { + $pQueryHash['service_where_sql'] .= $queryHash['where_sql']; + } + if( !empty( $queryHash['bind_vars'] )) { + if ( is_array( $pQueryHash['service_bind_vars'] )) { + $pQueryHash ['service_bind_vars']= array_merge( $pQueryHash['service_bind_vars'], $queryHash['bind_vars'] ); + } else { + $pQueryHash['service_bind_vars'] = $queryHash['bind_vars']; + } + } + // }}} + } + } + } + } + } + + /** + * Convert a built up pQueryHash into a single query string and set of bind variables. + * + * A pQueryHash is an array with required keys select and from, and optional keys join, where and order. + * Each key other than order should be an array with an 'sql' key which points to an array with statements. + * Statements should not include the keywords to start them excluding join statements nor should they + * include trailing delimeters such as commas as the conversion adds these where required. + * All where statments are automatically ANDed together. + * Each key other than order can optionally have a 'vars' key which points to an array with bind variables. + * The order key can either be an array or a single value. convertSortmode is automatically called on each order + * statement and built into the ORDER BY clause with delimeters where required. + * + * @return Results come back in $pQueryHash['query'] $pQueryHash['bind_vars'] and $pQueryHash['query_count'] if requested + * @TODO this function still contains legacy code. + */ + function convertQueryHash( &$pQueryHash, $pCountQuery = FALSE ) { + global $gBitSystem; + + // initiate some variables + if( empty( $pQueryHash['query'] )) { + $pQueryHash['query'] = ''; + } + + if( empty( $pQueryHash['query_count'] )) { + $pQueryHash['query_count'] = ''; + } + + if( empty( $pQueryHash['bind_vars'] )) { + $pQueryHash['bind_vars'] = array(); + } + + // Build up all the parts of the query + $queryParts = array( 'select', 'from', 'join', 'where' ); + foreach( $queryParts as $part ) { + if( !empty( $pQueryHash[$part] ) && !empty( $pQueryHash[$part]['sql'] )) { + // Add the required keyword -- joins include their own + if( $part != 'join' ) { + $pQueryHash['query'] .= strtoupper( " $part " ); + if( $pCountQuery ) { + $pQueryHash['query_count'] .= strtoupper( " $part " ); + } + } + + // Add the count for the count query + if( $pCountQuery && $part == 'select' ) { + $pQueryHash['query_count'] .= 'COUNT( '; + } + + $first = TRUE; + foreach( $pQueryHash[$part]['sql'] as $sql ) { + if( !$first ) { + // WHERE clauses have an implicit AND over all terms + if( $part == 'where' ) { + $pQueryHash['query'] .= " AND "; + if( $pCountQuery ) { + $pQueryHash['query_count'] .= " AND "; + } + } elseif( $part == 'select' || $part == 'from' ) { + $pQueryHash['query'] .= ", "; + if( $pCountQuery ) { + $pQueryHash['query_count'] .= ", "; + } + } + } else { + $first = FALSE; + } + + $pQueryHash['query'] .= $sql; + if( $pCountQuery ) { + $pQueryHash['query_count'] .= $sql; + } + } + + // Close the count for the count query + if( $pCountQuery && $part == 'select' ) { + $pQueryHash['query_count'] .= ' )'; + } + + if( !empty( $pQueryHash[$part]['var'] )) { + $pQueryHash['bind_vars'] = array_merge( $pQueryHash['bind_vars'], $pQueryHash[$part]['var'] ); + } + } + + // TODO: clean out this legacy code {{{ + // append old style serivce sql arguments + // since we don't allow bind_vars in the old services style, we can append everything here and then later on add the bind vars + if( !empty( $pQueryHash['service_'.$part.'_sql'] )) { + $pQueryHash['query'] .= $pQueryHash['service_'.$part.'_sql']; + if( $pCountQuery ) { + $pQueryHash['query_count'] .= $pQueryHash['service_'.$part.'_sql']; + } + } + // }}} + } + + // TODO: clean out this legacy code {{{ + // append legacy service bind vars + if( !empty( $pQueryHash['service_bind_vars'] )) { + $pQueryHash['bind_vars'] = array_merge( $pQueryHash['bind_vars'], $pQueryHash['service_bind_vars'] ); + } + /// }}} + + // Order can be a single value or an array of values all of which get passed to convertSortmode + if( !empty( $pQueryHash['order'] )) { + if( is_array( $pQueryHash['order'] )) { + $first = true; + foreach( $pQueryHash['order'] as $order ) { + if( !$first ) { + $pQueryHash['query'] .= ', '; + } else { + $pQueryHash['query'] .= ' ORDER BY '; + $first = false; + } + $pQueryHash['query'] .= $gBitSystem->mDb->convertSortmode( $order ); + } + } else { + $pQueryHash['query'] .= ' ORDER BY '.$gBitSystem->mDb->convertSortmode( $pQueryHash['order'] ); + } + } + } + + /** + * Set up SQL strings for services used by the object + * TODO: set this function deprecated and eventually nuke it + */ + function getServicesSql( $pServiceFunction, &$pSelectSql, &$pJoinSql, &$pWhereSql, &$pBindVars, $pObject = NULL, &$pParamHash = NULL ) { + //deprecated( 'You package is calling the deprecated LibertyContent::getServicesSql() method. Please update your code to use LibertyContent::getLibertySql' ); + global $gLibertySystem; + if( $loadFuncs = $gLibertySystem->getServiceValues( $pServiceFunction ) ) { + foreach( $loadFuncs as $func ) { + if( function_exists( $func ) ) { + if( !empty( $pObject ) && is_object( $pObject ) ) { + $loadHash = $func( $pObject, $pParamHash ); + } else { + $loadHash = $func( $this, $pParamHash ); + } + if( !empty( $loadHash['select_sql'] ) ) { + $pSelectSql .= $loadHash['select_sql']; + } + if( !empty( $loadHash['join_sql'] ) ) { + $pJoinSql .= $loadHash['join_sql']; + } + if( !empty( $loadHash['where_sql'] ) ) { + $pWhereSql .= $loadHash['where_sql']; + } + if( !empty( $loadHash['bind_vars'] ) ) { + if ( is_array( $pBindVars ) ) { + $pBindVars = array_merge( $pBindVars, $loadHash['bind_vars'] ); + } else { + $pBindVars = $loadHash['bind_vars']; + } + } + } + } + } + } + + // -------------------------------- Content Permission Functions + + /** + * Check to see if the loaded content has individually assigned permissions + * + * @access public + * @return Number of custom assigned permissions set for the loaded content item + */ + function hasUserPermissions() { + $ret = FALSE; + if( $this->isValid() ) { + $ret = $this->mDb->getOne( "SELECT COUNT(`perm_name`) FROM `".BIT_DB_PREFIX."liberty_content_permissions` WHERE `content_id` = ?", array( $this->mContentId )); + } + return $ret; + } + + /** + * getContentPermissionsSql + * + * @param array $pPermName + * @param array $pSelectSql + * @param array $pJoinSql + * @param array $pWhereSql + * @param array $pBindVars + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getContentPermissionsSql( $pPermName, &$pSelectSql, &$pJoinSql, &$pWhereSql, &$pBindVars ) { + global $gBitUser; + if ( defined('ROLE_MODEL') ) { + $pJoinSql .= " + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_permissions` lcperm ON (lc.`content_id`=lcperm.`content_id`) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_roles_map` urm ON (urm.`role_id`=lcperm.`role_id`) "; + $pWhereSql .= " OR (lcperm.perm_name=? AND (urm.user_id=? OR urm.user_id=?)) "; + } else { + $pJoinSql .= " + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_permissions` lcperm ON (lc.`content_id`=lcperm.`content_id`) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_groups_map` ugm ON (ugm.`group_id`=lcperm.`group_id`) "; + $pWhereSql .= " OR (lcperm.perm_name=? AND (ugm.user_id=? OR ugm.user_id=?)) "; + } + $pBindVars[] = $pPermName; + $pBindVars[] = $gBitUser->mUserId; + $pBindVars[] = ANONYMOUS_USER_ID; + } + + /** + * getContentListPermissionsSql + * + * @param array $pPermName + * @param array $pSelectSql + * @param array $pJoinSql + * @param array $pWhereSql + * @param array $pBindVars + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + public static function getContentListPermissionsSql( $pPermName, &$pSelectSql, &$pJoinSql, &$pWhereSql, &$pBindVars ) { + global $gBitUser; + if ( defined('ROLE_MODEL') ) { + $pJoinSql .= " + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_permissions` lcperm ON (lc.`content_id`=lcperm.`content_id`) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_roles_map` urm ON (urm.`role_id`=lcperm.`role_id`) "; + $pWhereSql .= " AND ( lcperm.perm_name IS NULL OR ( lcperm.perm_name=? AND urm.user_id=? AND ( (lcperm.is_revoked !=? OR lcperm.is_revoked IS NULL) OR lc.`user_id`=? ) ) )"; + } else { + $pJoinSql .= " + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_permissions` lcperm ON (lc.`content_id`=lcperm.`content_id`) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_groups_map` ugsm ON (ugsm.`group_id`=lcperm.`group_id`) "; + $pWhereSql .= " AND ( lcperm.perm_name IS NULL OR ( lcperm.perm_name=? AND ugsm.user_id=? AND ( (lcperm.is_revoked !=? OR lcperm.is_revoked IS NULL) OR lc.`user_id`=? ) ) )"; + } + $pBindVars[] = $pPermName; + $pBindVars[] = $gBitUser->mUserId; + $pBindVars[] = "y"; + $pBindVars[] = $gBitUser->mUserId; + } + + /** + * Check is a user has permission to access the object + * + * @param integer User Identifier + * @param integer Content Itentifier + * @param string Content Type GUID + * @param string Name of the permission + * @return bool true if access is allowed + */ + function checkContentPermission( $pParamHash ) { + global $gBitUser; + + $ret = FALSE; + + if( !empty( $this->mAdminContentPerm ) && $gBitUser->hasPermission( $this->mAdminContentPerm ) ) { + // content admin shortcut + $ret = TRUE; + } else { + $selectSql = ''; $joinSql = ''; $whereSql = ''; + $bindVars = array(); + + if( !empty( $pParamHash['content_id'] ) ) { + $bindVars[] = $pParamHash['content_id']; + } elseif( $this->isValid() ) { + $bindVars[] = $this->mContentId; + } + + if( @$this->verifyId( $pParamHash['user_id'] ) ) { + $whereSql .= " AND lc.`user_id` = ? "; + $bindVars[] = $pParamHash['user_id']; + } + + if( !empty( $pParamHash['group_id'] ) ) { + $whereSql .= " AND lcperm.`group_id` = ? "; + $bindVars[] = $pParamHash['group_id']; + } + + if( !empty( $pParamHash['role_id'] ) ) { + $whereSql .= " AND lcperm.`role_id` = ? "; + $bindVars[] = $pParamHash['role_id']; + } + + $permWhereSql = ''; + $this->getContentPermissionsSql( $pParamHash['perm_name'], $selectSql, $joinSql, $permWhereSql, $bindVars ); + + if( !empty( $whereSql ) ) { + $whereSql = preg_replace( '/^[\s]*AND/', ' ', $whereSql ); + } + + $query = "SELECT COUNT(*) + FROM `".BIT_DB_PREFIX."liberty_content` lc $joinSql + WHERE lc.`content_id`=? AND ( $whereSql $permWhereSql ) "; + $ret = $this->mDb->getOne( $query, $bindVars ); + } + return( !empty( $ret ) ); + } + + /** + * Load all permissions assigned to a given object. + * This function is mainly used to fetch a list of custom permissions of a given content item. + * + * @access public + */ + function getContentPermissionsList() { + global $gBitUser; + $ret = FALSE; + if( $this->isValid() ) { + if ( defined('ROLE_MODEL') ) { + $query = " + SELECT lcperm.`perm_name`, lcperm.`is_revoked`, ur.`role_id`, ur.`role_name`, up.`perm_desc` + FROM `".BIT_DB_PREFIX."liberty_content_permissions` lcperm + INNER JOIN `".BIT_DB_PREFIX."users_roles` ur ON( lcperm.`role_id`=ur.`role_id` ) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_permissions` up ON( up.`perm_name`=lcperm.`perm_name` ) + WHERE lcperm.`content_id` = ?"; + $team = 'role_id'; + } else { + $query = " + SELECT lcperm.`perm_name`, lcperm.`is_revoked`, ug.`group_id`, ug.`group_name`, up.`perm_desc` + FROM `".BIT_DB_PREFIX."liberty_content_permissions` lcperm + INNER JOIN `".BIT_DB_PREFIX."users_groups` ug ON( lcperm.`group_id`=ug.`group_id` ) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_permissions` up ON( up.`perm_name`=lcperm.`perm_name` ) + WHERE lcperm.`content_id` = ?"; + $team = 'group_id'; + } + $perms = $this->mDb->getAll( $query, array( $this->mContentId )); + foreach( $perms as $perm ) { + $ret[$perm[$team]][$perm['perm_name']] = $perm; + } + } + return $ret; + } + + /** + * Get a list of content with permissions + * + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + public static function getContentWithPermissionsList() { + global $gBitSystem; + $ret = array(); + if ( defined('ROLE_MODEL') ) { + $query = " + SELECT lcperm.`perm_name`, lc.`title`, lc.`content_id`, lc.`content_type_guid`, lcperm.`is_revoked`, ur.`role_id`, ur.`role_name`, up.`perm_desc` + FROM `".BIT_DB_PREFIX."liberty_content_permissions` lcperm + INNER JOIN `".BIT_DB_PREFIX."users_roles` ur ON( lcperm.`role_id`=ur.`role_id` ) + INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON( lcperm.`content_id`=lc.`content_id` ) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_permissions` up ON( up.`perm_name`=lcperm.`perm_name` ) + ORDER BY ".$gBitSystem->mDb->convertSortmode( 'content_type_guid_asc' ).", ".$gBitSystem->mDb->convertSortmode( 'title_asc' ); + } else { + $query = " + SELECT lcperm.`perm_name`, lc.`title`, lc.`content_id`, lc.`content_type_guid`, lcperm.`is_revoked`, ug.`group_id`, ug.`group_name`, up.`perm_desc` + FROM `".BIT_DB_PREFIX."liberty_content_permissions` lcperm + INNER JOIN `".BIT_DB_PREFIX."users_groups` ug ON( lcperm.`group_id`=ug.`group_id` ) + INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON( lcperm.`content_id`=lc.`content_id` ) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_permissions` up ON( up.`perm_name`=lcperm.`perm_name` ) + ORDER BY ".$gBitSystem->mDb->convertSortmode( 'content_type_guid_asc' ).", ".$gBitSystem->mDb->convertSortmode( 'title_asc' ); + } + $perms = $gBitSystem->mDb->getAll( $query ); + foreach( $perms as $perm ) { + $ret[$perm['content_type_guid']][$perm['content_id']][] = $perm; + } + return $ret; + } + + /** + * Expunge Content Permissions + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function expungeContentPermissions() { + $ret = FALSE; + if( $this->isValid() ) { + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_content_permissions` WHERE `content_id` = ?"; + $ret = $this->mDb->query( $query, array( $this->mContentId )); + } + return $ret; + } + + /** + * Function that determines if this content specified permission for the current gBitUser, and will throw a fatal error if not. + * + * @param string Name of the permission to check + * @param string Message if permission denigned + */ + function verifyUserPermission( $pPermName, $pFatalMessage = NULL ) { + $ret = TRUE; + if( $this->isValid() && !$this->hasUserPermission( $pPermName ) ) { + global $gBitSystem; + $gBitSystem->fatalPermission( $pPermName, $pFatalMessage ); + } + return $ret; + } + + /** + * Function that determines if this content specified permission for the current gBitUser. + * Assigned content perms override the indvidual global perms, so the result is the union of the global permission set + overridden individual content perms + * + * @param string Name of the permission to check + * @param string Check access control service if available + * @param string return default user permission setting when no content perms are set + * @return bool true if user has permission to access file + */ + function hasUserPermission( $pPermName, $pVerifyAccessControl=TRUE ) { + global $gBitUser; + $ret = FALSE; + if( !$this->isValid() ) { + // return default user permission setting when no content is loaded + $ret = $gBitUser->hasPermission( $pPermName ); + } elseif( !$gBitUser->isRegistered() || !( $ret = $this->isOwner() || $ret = $gBitUser->isAdmin() )) { + if( $gBitUser->isAdmin() || $gBitUser->hasPermission( $this->mAdminContentPerm )) { + $ret = TRUE; + } else { + if( $pVerifyAccessControl ) { + $this->verifyAccessControl(); + } + $checkPerms = $this->getUserPermissions(); + if ( !empty( $checkPerms ) ) { + // Do they have the admin permission or the one we want? + if ( !empty( $checkPerms[$this->mAdminContentPerm] ) ) { + $ret = TRUE; + } elseif ( !empty( $checkPerms[$pPermName] ) ) { + $ret = TRUE; + } + } else { + // return default user permission setting when no content perms are set + $ret = $gBitUser->hasPermission( $pPermName ); + } + } + } + return( $ret ); + } + + /** + * Determine if current user has the ability to administer this type of content + * + * @return bool True if user has this type of content administration permission + */ + function hasAdminPermission( $pVerifyAccessControl=TRUE ) { + return( $this->hasUserPermission( $this->mAdminContentPerm, $pVerifyAccessControl ) ); + } + + // === verifyAdminPermission + /** + * This code was duplicated _EVERYWHERE_ so here is an easy template to cut that down. + * It will verify if a given user has a given $permission and if not, it will display the error template and die() + * @param $pVerifyAccessControl check access control service if available + * @return TRUE if permitted, method will fatal out if not + * @access public + */ + function verifyAdminPermission( $pVerifyAccessControl=TRUE ) { + global $gBitSystem; + if( $this->hasAdminPermission( $pVerifyAccessControl ) ) { + return TRUE; + } else { + $gBitSystem->fatalPermission( $this->mAdminContentPerm ); + } + } + + /** + * Determine if current user has the ability to delete/expunge this type of content + * + * @return bool True if user has this type of content expunge permission + */ + function hasExpungePermission( $pVerifyAccessControl=TRUE ) { + return( $this->hasUserPermission( $this->mExpungeContentPerm, $pVerifyAccessControl ) ); + } + + // === verifyExpungePermission + /** + * It will verify if a given user has a given $permission and if not, it will display the error template and die() + * @param $pVerifyAccessControl check access control service if available + * @return TRUE if permitted, method will fatal out if not + * @access public + */ + function verifyExpungePermission( $pVerifyAccessControl=TRUE ) { + global $gBitSystem; + if( $this->hasExpungePermission( $pVerifyAccessControl ) ) { + return TRUE; + } else { + $gBitSystem->fatalPermission( $this->mExpungeContentPerm ); + } + } + + /** + * Determine if current user has the ability to edit this type of content + * + * @return bool True if user has this type of content administration permission + */ + function hasUpdatePermission( $pVerifyAccessControl=TRUE ) { + return( $this->hasUserPermission( $this->mUpdateContentPerm, $pVerifyAccessControl ) ); + } + + /** + * Deprecated, use hasUpdatePermission + * + * @return bool True if user has this type of content administration permission + */ + function hasEditPermission( $pVerifyAccessControl=TRUE, $pCheckGlobalPerm=TRUE ) { + deprecated( "LibertyContent::hasEditPermission has been replaced with LibertyContent::hasUpdatePermission and pCheckGlobal has been change to always be the case" ); + return( $this->hasUpdatePermission( $pVerifyAccessControl ) ); + } + + // === verifyUpdatePermission + /** + * This code was duplicated _EVERYWHERE_ so here is an easy template to cut that down. + * It will verify if a given user has a given $permission and if not, it will display the error template and die() + * @param $pVerifyAccessControl check access control service if available + * @return TRUE if permitted, method will fatal out if not + * @access public + */ + function verifyUpdatePermission( $pVerifyAccessControl=TRUE ) { + global $gBitSystem; + if( $this->hasUpdatePermission( $pVerifyAccessControl ) ) { + return TRUE; + } else { + $gBitSystem->fatalPermission( $this->mUpdateContentPerm ); + } + } + + // === verifyEditPermission + /** + * Deprecated, use verifyUpdatePermission + */ + function verifyEditPermission( $pVerifyAccessControl=TRUE, $pCheckGlobalPerm=TRUE ) { + deprecated( "LibertyContent::verifyEditPermission has been replaced with LibertyContent::verifyUpdatePermission and pCheckGlobal has been change to always be the case" ); + $this->verifyUpdatePermission( $pVerifyAccessControl ); + } + + /** + * Determine if current user has the ability to craete this type of content + * + * @return bool True if user has this type of content administration permission + */ + function hasCreatePermission( $pVerifyAccessControl=TRUE ) { + return( $this->hasUserPermission( $this->mCreateContentPerm, $pVerifyAccessControl ) ); + } + + // === verifyCreatePermission + /** + * Determine if current user has the ability to create this type of content + * Note this will always return FALSEif the content isValid + * + * @return bool True if user has this type of content administration permission + **/ + function verifyCreatePermission( $pVerifyAccessControl=TRUE ) { + global $gBitSystem; + if( !$this->isValid() && $this->hasCreatePermission( $pVerifyAccessControl ) ) { + return TRUE; + } else { + $gBitSystem->fatalPermission( $this->mCreateContentPerm ); + } + } + + /** + * Determine if current user has the ability to view this type of content + * Note that this will always return TRUE if you haven't set the mViewContentPerm in your class + * + * @return bool True if user has this type of content administration permission + */ + function hasViewPermission( $pVerifyAccessControl=TRUE ) { + return( $this->hasUpdatePermission( $pVerifyAccessControl ) || empty( $this->mViewContentPerm ) || $this->hasUserPermission( $this->mViewContentPerm, $pVerifyAccessControl )); + } + + // === verifyViewPermission + /** + * This code was duplicated _EVERYWHERE_ so here is an easy template to cut that down. + * It will verify if a given user has a given $permission and if not, it will display the error template and die() + * @param $pVerifyAccessControl check access control service if available + * @return TRUE if permitted, method will fatal out if not + * @access public + */ + function verifyViewPermission( $pVerifyAccessControl=TRUE ) { + global $gBitSystem; + if( $this->hasViewPermission( $pVerifyAccessControl ) ) { + return TRUE; + } else { + $gBitSystem->fatalPermission( $this->mViewContentPerm ); + } + } + + /** + * Determine if current user has the ability to post comments to this type of content + * + * @return bool True if user has this type of content administration permission + */ + function hasPostCommentsPermission( $pVerifyAccessControl=TRUE ) { + return( $this->hasUserPermission( 'p_liberty_post_comments', $pVerifyAccessControl )); + } + + // === verifyPostCommentsPermission + /** + * It will verify if a given user has a given $permission and if not, it will display the error template and die() + * @param $pVerifyAccessControl check access control service if available + * @return TRUE if permitted, method will fatal out if not + * @access public + */ + function verifyPostCommentsPermission( $pVerifyAccessControl=TRUE ) { + global $gBitSystem; + if( $this->hasPostCommentPermission( $pVerifyAccessControl ) ) { + return TRUE; + } else { + $gBitSystem->fatalPermission( 'p_liberty_post_comments' ); + } + } + + /** + * Get specific permissions for the specified user for this content + * + * @return Array of all permissions for the current user joined with perms + * for the current content. This should handle cases where + * non-default permissions is assigned, default permission is + * removed, and duplicate default permissions where one team's perm + * is revoked, but another is still permitted. If the permission is + * revoked, is_revoked will be set to 'y' + */ + function getUserPermissions() { + global $gBitUser; + + $userId = $gBitUser->mUserId; + // Prevent null entires when creating database + if( !is_numeric( $userId ) ) $userId = 0; + if( !is_numeric( $this->mContentId ) ) $this->mContentId = 0; + if( !isset( $this->mUserContentPerms )) { + // get the default permissions for specified user + if ( defined('ROLE_MODEL') ) { + $query = " + SELECT urp.`perm_name` as `hash_key`, 1 as `role_perm`, urp.`perm_name`, urp.`perm_value`, urp.`role_id` + FROM `".BIT_DB_PREFIX."users_roles_map` urm + LEFT JOIN `".BIT_DB_PREFIX."users_role_permissions` urp ON(urm.`role_id`=urp.`role_id`) + LEFT JOIN `".BIT_DB_PREFIX."liberty_content_permissions` lcp ON(lcp.`role_id`=urm.`role_id` AND lcp.`content_id`=? AND urp.`perm_name`=lcp.`perm_name`) + WHERE (urm.`user_id`=? OR urm.`user_id`=?) AND lcp.`perm_name` IS NULL"; + } else { + $query = " + SELECT ugp.`perm_name` as `hash_key`, 1 as `group_perm`, ugp.`perm_name`, ugp.`perm_value`, ugp.`group_id` + FROM `".BIT_DB_PREFIX."users_groups_map` ugm + LEFT JOIN `".BIT_DB_PREFIX."users_group_permissions` ugp ON(ugm.`group_id`=ugp.`group_id`) + LEFT JOIN `".BIT_DB_PREFIX."liberty_content_permissions` lcp ON(lcp.`group_id`=ugm.`group_id` AND lcp.`content_id`=? AND ugp.`perm_name`=lcp.`perm_name`) + WHERE (ugm.`user_id`=? OR ugm.`user_id`=?) AND lcp.`perm_name` IS NULL"; + } + if( !$defaultPerms = $this->mDb->getAssoc( $query, array( $this->mContentId, $userId, ANONYMOUS_USER_ID ) ) ) { + $defaultPerms = array(); + } + if ( defined('ROLE_MODEL') ) { + $query = " + SELECT lcp.`perm_name` AS `hash_key`, lcp.* + FROM `".BIT_DB_PREFIX."liberty_content_permissions` lcp + INNER JOIN `".BIT_DB_PREFIX."users_roles_map` urm ON(lcp.role_id=urm.role_id) + LEFT JOIN `".BIT_DB_PREFIX."users_role_permissions` urp ON(urm.role_id=urp.role_id AND urp.role_id!=lcp.role_id AND urp.perm_name=lcp.perm_name) + WHERE lcp.content_id=? AND (urm.user_id=? OR urm.user_id=?) AND lcp.is_revoked IS NULL"; + } else { + $query = " + SELECT lcp.`perm_name` AS `hash_key`, lcp.* + FROM `".BIT_DB_PREFIX."liberty_content_permissions` lcp + INNER JOIN `".BIT_DB_PREFIX."users_groups_map` ugm ON(lcp.group_id=ugm.group_id) + LEFT JOIN `".BIT_DB_PREFIX."users_group_permissions` ugp ON(ugm.group_id=ugp.group_id AND ugp.group_id!=lcp.group_id AND ugp.perm_name=lcp.perm_name) + WHERE lcp.content_id=? AND (ugm.user_id=? OR ugm.user_id=?) AND lcp.is_revoked IS NULL"; + } + if( !$nonDefaultPerms = $this->mDb->getAssoc( $query, array( $this->mContentId, $userId, ANONYMOUS_USER_ID ) ) ) { + $nonDefaultPerms = array(); + } + + $this->mUserContentPerms = array_merge( $defaultPerms, $nonDefaultPerms ); + + $this->invokeServices( 'content_user_perms_function' ); + } + + return $this->mUserContentPerms; + } + + /** + * Store a permission for the object that has been loaded in the permission database + * + * Any old copy of the permission is deleted prior to loading the new copy + * @param integer Group Identifier + * @param string Name of the permission + * @param integer Content Itentifier + * @return bool true ( will not currently report a failure ) + */ + function storePermission( $pTeamId, $pPermName, $pIsRevoked=FALSE, $pContentId=NULL ){ + $ret = FALSE; + $pContentId = $pContentId == NULL?$this->mContentId:$pContentId; + if( @BitBase::verifyId( $pGroupId ) && !empty( $pPermName ) && @BitBase::verifyId( $pContentId ) ) { + $this->removePermission( $pGroupId, $pPermName, $pContentId ); + $storeHash = array( + 'perm_name' => $pPermName, + 'content_id' => $pContentId, + ); + if ( defined('ROLE_MODEL') ) { + $storeHash['role_id'] = $pTeamId; + } else { + $storeHash['group_id'] = $pTeamId; + } + // check to see if this is an exclusion + if( $pIsRevoked ) { + $storeHash['is_revoked'] = 'y'; + } + $ret = $this->mDb->associateInsert( BIT_DB_PREFIX."liberty_content_permissions", $storeHash ); + } + return $ret; + } + + /** + * Remove a permission to access the content + * + * @param integer Group Identifier + * @param string Name of the permission + * @return bool true ( will not currently report a failure ) + */ + function removePermission( $pTeamId, $pPermName, $pContentId=NULL ) { + $pContentId = $pContentId == NULL?$this->mContentId:$pContentId; + if( @BitBase::verifyId( $pTeamId ) && !empty( $pPermName ) && @BitBase::verifyId( $pContentId ) ) { + if ( defined('ROLE_MODEL') ) { + $team = 'role_id'; + } else { + $team = 'group_id'; + } + $query = " + DELETE FROM `".BIT_DB_PREFIX."liberty_content_permissions` + WHERE `$team` = ? and `content_id` = ? and `perm_name` = ?"; + $bindVars = array( $pTeamId, $pContentId, $pPermName ); + $result = $this->mDb->query( $query, $bindVars ); + } + return TRUE; + } + + /** + * Check to see if this permission is already in the global permissions table. + * + * @param array $pTeamId + * @param array $pPermName + * @access public + * @return TRUE if present, FALSE if not + */ + function isExcludedPermission( $pTeamId, $pPermName ) { + if( @BitBase::verifyId( $pTeamId ) && !empty( $pPermName )) { + if ( defined('ROLE_MODEL') ) { + $query = "SELECT `perm_name` FROM `".BIT_DB_PREFIX."users_role_permissions` WHERE `role_id` = ? AND `perm_name` = ?"; + } else { + $query = "SELECT `perm_name` FROM `".BIT_DB_PREFIX."users_group_permissions` WHERE `group_id` = ? AND `perm_name` = ?"; + } + return( $this->mDb->getOne( $query, array( $pTeamId, $pPermName )) == $pPermName ); + } + } + + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Preferences Functions + + /** + * Returns the content preferences value for the passed in key. + * + * @param string Hash key for the mPrefs value + * @param string Default value to return if the preference is empty + * @param int Optional content_id for arbitrary content preference + */ + public static function getContentPreference( $pContentId, $pPrefName, $pPrefDefault=NULL ) { + global $gBitDb; + $ret = NULL; + + if( parent::verifyId( $pContentId ) && !empty( $pPrefName )) { + // Get a user preference for an arbitrary user + $sql = "SELECT `pref_value` FROM `".BIT_DB_PREFIX."liberty_content_prefs` WHERE `content_id`=? AND `pref_name`=?"; + + if( !$ret = $gBitDb->getOne( $sql, array( $pContentId, $pPrefName ) ) ) { + $ret = $pPrefDefault; + } + } + return $ret; + } + + /** + * Returns the content preferences value for the passed in key. + * + * @param string Hash key for the mPrefs value + * @param string Default value to return if the preference is empty + * @param int Optional content_id for arbitrary content preference + */ + function getPreference( $pPrefName, $pPrefDefault=NULL ) { + global $gBitDb; + $ret = NULL; + + if( $this->isValid() ) { + if( is_null( $this->mPrefs ) ) { + $this->loadPreferences(); + } + if( isset( $this->mPrefs ) && isset( $this->mPrefs[$pPrefName] ) ) { + $ret = $this->mPrefs[$pPrefName]; + } else { + $ret = $pPrefDefault; + } + } + return $ret; + } + + /** + * loadPreferences of the currently loaded object or pass in to get preferences of a specific content_id + * + * @param numeric $pContentId content_id of the item we want the prefs from (optional) + * @access public + * @return array of preferences if $pContentId is set or pass preferences on to $this->mPrefs + */ + function loadPreferences( $pContentId = NULL ) { + global $gBitSystem; + if( @BitBase::verifyId( $pContentId )) { + return $gBitSystem->mDb->getAssoc( "SELECT `pref_name`, `pref_value` FROM `".BIT_DB_PREFIX."liberty_content_prefs` WHERE `content_id`=?", array( $pContentId )); + } elseif( $this->isValid() ) { + // If no results, getAssoc will return an empty array (ie not a true NULL value) so getPreference can tell we have attempted a load + $this->mPrefs = @$this->mDb->getAssoc( "SELECT `pref_name`, `pref_value` FROM `".BIT_DB_PREFIX."liberty_content_prefs` WHERE `content_id`=?", array( $this->mContentId )); + } + } + + /** + * Set a hash value in the mPrefs hash. This does *NOT* store the value in the database. It does no checking for existing or duplicate values. the main point of this function is to limit direct accessing of the mPrefs hash. I will probably make mPrefs private one day. + * + * @param string Hash key for the mPrefs value + * @param string Value for the mPrefs hash key + */ + function setPreference( $pPrefName, $pPrefValue ) { + $this->mPrefs[$pPrefName] = $pPrefValue; + } + + + /** + * Saves a preference to the liberty_content_prefs database table with the given pref name and value. If the value is NULL, the existing value will be delete and the value will not be saved. However, a zero will be stored. This will update the mPrefs hash. + * + * @param string Hash key for the mPrefs value + * @param string Value for the mPrefs hash key + */ + function storePreference( $pPrefName, $pPrefValue = NULL ) { + $ret = FALSE; + if( LibertyContent::isValid() ) { + $this->StartTrans(); + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_content_prefs` WHERE `content_id`=? AND `pref_name`=?"; + $bindvars = array( $this->mContentId, $pPrefName ); + $result = $this->mDb->query($query, $bindvars); + if( !is_null( $pPrefValue )) { + $query = "INSERT INTO `".BIT_DB_PREFIX."liberty_content_prefs` (`content_id`,`pref_name`,`pref_value`) VALUES(?, ?, ?)"; + $bindvars[] = substr( $pPrefValue, 0, 510 ); + $result = $this->mDb->query( $query, $bindvars ); + $this->mPrefs[$pPrefName] = $pPrefValue; + } + $this->mPrefs[$pPrefName] = $pPrefValue; + $this->CompleteTrans(); + $this->clearFromCache(); + } + return $ret; + } + + /** + * Register the content type for reference + * + * @param string Content Type GUID + * @param array Array of content type data + * Populates the mType array with the following entries + * string content_type_guid + * string + */ + function registerContentType( $pContentGuid, $pTypeParams ) { + global $gLibertySystem; + $gLibertySystem->registerContentType( $pContentGuid, $pTypeParams ); + $this->mType = $pTypeParams; + } + + /** + * Increment the content item hit flag by 1 + * + * @return bool true ( will not currently report a failure ) + */ + function addHit() { + global $gBitUser,$gBitSystem; + if( empty( $_REQUEST['post_comment_submit'] ) && empty( $_REQUEST['post_comment_request'] ) ) { + if( @BitBase::verifyId( $this->mContentId ) && (( $gBitUser->isRegistered() && !$this->isOwner() ) || ( $gBitUser->getField( 'user_id' ) == ANONYMOUS_USER_ID )) && ( $gBitSystem->isFeatureActive( 'users_count_admin_pageviews' ) || !$gBitUser->isAdmin() ) ) { + if( $this->mDb->getOne( "SELECT `content_id` FROM `".BIT_DB_PREFIX."liberty_content_hits` WHERE `content_id`=?", array( $this->mContentId ))) { + $query = "UPDATE `".BIT_DB_PREFIX."liberty_content_hits` SET `hits`=`hits`+1, `last_hit`= ? WHERE `content_id` = ?"; + } else { + $query = "INSERT INTO `".BIT_DB_PREFIX."liberty_content_hits` ( `hits`, `last_hit`, `content_id` ) VALUES ( ?,?,? )"; + $bindVars[] = 1; + } + $bindVars[] = $gBitSystem->getUTCTime(); + $bindVars[] = $this->mContentId; + $this->StartTrans(); + $result = $this->mDb->query( $query, $bindVars ); + $this->CompleteTrans(); + } + } + return TRUE; + } + + /** + * Set Hits and Last Hit + * + * @return bool true ( will not currently report a failure ) + */ + function setHits($pHits, $pLastHit=0) { + if( $this->mContentId && !empty($pHits) ) { + $query = "UPDATE `".BIT_DB_PREFIX."liberty_content_hits` SET `hits`= ?, `last_hit`= ? WHERE `content_id` = ?"; + $result = $this->mDb->query( $query, array( $pHits, $pLastHit, $this->mContentId ) ); + $affected_rows = $this->mDb->Affected_Rows(); + if( !$affected_rows ) { + $query = "INSERT INTO `".BIT_DB_PREFIX."liberty_content_hits` ( `hits`, `last_hit`, `content_id` ) VALUES (?,?,?)"; + $result = $this->mDb->query( $query, array( $pHits, $pLastHit, $this->mContentId ) ); + } + } + return TRUE; + } + + + /** + * Get Hits and Last Hit + * + * @return bool true ( will not currently report a failure ) + */ + function getHits() { + if( $this->mContentId ) { + $query = "SELECT `hits`,`last_hit` FROM `".BIT_DB_PREFIX."liberty_content_hits` where `content_id` = ?"; + $row = $this->mDb->getRow( $query, array( $this->mContentId ) ); + if ( !empty($row) ) { + $this->mInfo['hits'] = $row['hits']; + $this->mInfo['last_hit'] = $row['last_hit']; + } + } + return TRUE; + } + + /** + * Create the generic title for a content item + * + * This can be overwriten by extended classes to provide an appropriate title string + * @param array pHash type hash of data to be used to provide base data + * @return string Descriptive title for the page + */ + public static function getTitleFromHash( &$pHash, $pDefault=TRUE ) { + $ret = NULL; + if( !empty( $pHash['title'] ) ) { + $ret = $pHash['title']; + } elseif( $pDefault && !empty( $pHash['content_name'] ) ) { + $ret = $pHash['content_name']; + } + return $ret; + } + + /** + * Create the generic title for a content item + * + * This will normally be overwriten by extended classes to provide + * an appropriate title string + * @return string Descriptive title for the page + */ + function getTitle() { + $ret = NULL; + if( $this->isValid() ) { + $ret = static::getTitleFromHash( $this->mInfo ); + } + return $ret; + } + + /** + * Get the time this object was created + * + * @return int Unix epoch of time object was created + */ + function getTimeCreated() { + $ret = NULL; + if( $this->isValid() ) { + $ret = $this->getField( 'created' ); + } + return $ret; + } + + /** + * Get the time this object was last modified + * + * @return int Unix epoch of time object was last modified + */ + function getTimeModified() { + $ret = NULL; + if( $this->isValid() ) { + $ret = $this->getField( 'last_modified' ); + } + return $ret; + } + + /** + * Attempt to create a brief description of this object, most useful for <meta name="description" /> + * + * @return array list of aliases + */ + function generateDescription() { + $ret = NULL; + if( $this->isValid() ) { + if( $this->getField('summary') ) { + $ret = $this->getField('summary'); + } elseif( $this->getField('data') ) { + $text = trim( preg_replace('/\s+/', ' ', strip_tags( $this->getParsedData() ) ) ); + // 250 to 300 is max description + $ret = substr( $text, 0, 250 ); + } + } + return $ret; + } + + /** + * Attempt to create a collection of relevant words about this object, most useful for <meta name="keywords" /> + * + * @return array list of aliases + */ + function generateKeywords() { + $ret = array(); + if( $this->isValid() ) { + } + return $ret; + } + + /** + * Get array of aliases for this content object + * + * @return array list of aliases + */ + function getAliases( $pUpperCase = FALSE ) { + global $gBitSystem; + $ret = array(); + if( $this->isValid() ) { + $selectColumn = ( $pUpperCase ? $gBitSystem->mDb->getCaseLessColumn('alias_title') : '`alias_title`' ); + $ret = $this->mDb->getCol( "SELECT ".$selectColumn." FROM `".BIT_DB_PREFIX."liberty_aliases` lal INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON(lal.`content_id`=lc.`content_id`) WHERE lal.`content_id`=? ", array( $this->mContentId ), BIT_QUERY_CACHE_TIME ); + } + return $ret; + } + + /** + * Access a content item type GUID + * + * @return string content_type_guid for the content + */ + function getContentType() { + $ret = NULL; + if( isset( $this->mInfo['content_type_guid'] ) ) { + $ret = $this->mInfo['content_type_guid']; + } elseif( $this->mContentTypeGuid ) { + // for unloaded classes + $ret = $this->mContentTypeGuid; + } elseif( $this->mType['content_type_guid'] ) { + // unloaded content might have this + $ret = $this->mType['content_type_guid']; + } + return $ret; + } + + /** + * Get the display name of the content type + * @param boolean $pPlural true will return the plural form of the content type display name + * @return string the display name of the content type + */ + function getContentTypeName( $pPlural=FALSE ){ + global $gLibertySystem; + return $gLibertySystem->getContentTypeName( $this->getContentType(), $pPlural ); + } + + + /** + * getContentTypeDescription + * + * @param array $pContentType + * @access public + * @return TRUE on success, FALSE on failure + */ + function getContentTypeDescription( $pContentType=NULL ) { + deprecated( 'You are calling the deprecated method getContentTypeDescription, use getContentTypeName( $pPlural )' ); + return $this->getContentTypeName(); + /* + global $gLibertySystem; + if( is_null( $pContentType ) ) { + $pContentType = $this->getContentType(); + } + return $gLibertySystem->getContentTypeDescription( $pContentType ); + */ + } + + /** + * Access a content item content_id + * + * @return string content_type_guid for the object + */ + function getContentId() { + $ret = NULL; + if( isset( $this->mContentId ) ) { + $ret = $this->mContentId; + } + return $ret; + } + + /** + * Return content type description for this content object. + * + * @return string content_type_guid description for the object + */ + function getContentDescription() { + deprecated( 'You are calling the deprecated method getContentDescription, use getContentTypeName( $pPlural )' ); + return $this->getContentTypeName(); + } + + + /** + * returns a path to the template type requested + * this is intended for package override. while not a requirement please use a naming convention of center_<action>_<content_type_guid>.tpl for new tpls + * + * @param string $pAction the type of template. common types are view and list + */ + function getViewTemplate( $pAction ) { + $ret = null; + switch ( $pAction ){ + case "view": + case "list": + $ret = "bitpackage:liberty/center_".$pAction."_generic.tpl"; + break; + } + return $ret; + } + + /** + * Pure virtual function that returns the include file that should render a page of content of this type + * @return the fully specified path to file to be included + */ + function getRenderFile() { + return LIBERTY_PKG_INCLUDE_PATH.'display_content_inc.php'; + } + + public function getDisplayLink( $pLinkText=NULL, $pAnchor=NULL ) { + return self::getDisplayLinkFromHash( $this->mInfo, $pLinkText, $pAnchor ); + } + + /** + * Pure virtual function that returns link to display a piece of content + * + * @param string $pLinkText Text for the link unless overriden by object title + * @param array $pParamHash different possibilities depending on derived class + * @param string $pAnchor anchor string e.g.: #comment_123 + * @return string Formated html the link to display the page. + */ + public static function getDisplayLinkFromHash( &$pParamHash, $pLinkText=NULL, $pAnchor=NULL ) { + global $gBitSmarty; + $ret = ''; + + if( empty( $pLinkText )) { + if( !empty( $pParamHash['title'] )) { + $pLinkText = $pParamHash['title']; + } elseif( !empty( $pParamHash['content_name'] ) ) { + $pLinkText = "[ ".$pParamHash['content_name']." ]"; + } + } + + if( empty( $pLinkText )) { + $pLinkText = "[ ".tra( "No Title" )." ]"; + } + + // we add some more info to the title of the link + if( !empty( $pParamHash['created'] )) { + $gBitSmarty->loadPlugin( 'smarty_modifier_bit_short_date' ); + $linkTitle = tra( 'Created' ).': '.smarty_modifier_bit_short_date( $pParamHash['created'] ); + } else { + $linkTitle = $pLinkText; + } + + // finally we are ready to create the full link + if( !empty( $pParamHash['content_id'] )) { + $ret = '<a title="'.htmlspecialchars( $linkTitle ).'" href="'.LibertyContent::getDisplayUrlFromHash( $pParamHash ).$pAnchor.'">'.htmlspecialchars( $pLinkText ).'</a>'; + } + return $ret; + } + + /** + * Not-so-pure virtual function that returns fully qualified URI to a piece of content + * @param string Text for DisplayLink function + * @param array different possibilities depending on derived class + * @return string Formated URL address to display the page. + */ + public function getDisplayUri() { + if( $this->isValid() ) { + return BIT_ROOT_URI.substr( static::getDisplayUrlFromHash( $this->mInfo ), strlen( BIT_ROOT_URL ) ); + } + } + + /** + * Not-so-pure virtual function that returns fully qualified URI to a piece of content + * @param string Text for DisplayLink function + * @param array different possibilities depending on derived class + * @return string Formated URL address to display the page. + */ + public static function getDisplayUriFromHash( &$pParamHash ) { + return BIT_ROOT_URI.substr( static::getDisplayUrlFromHash( $pParamHash ), strlen( BIT_ROOT_URL ) ); + } + + /** + * Not-so-pure virtual function that returns Request_URI to a piece of content + * @param array $pParamHash a hash of params to add to the url + * @return string Formated URL address to display the page. + */ + public static function getDisplayUrlFromHash( &$pParamHash ) { + $ret = NULL; + if( @static::verifyId( $pParamHash['content_id'] ) ) { + $ret = BIT_ROOT_URL.'index.php?content_id='.$pParamHash['content_id']; + } + return $ret; + } + + /** + * Returns Request URL to a piece of content + */ + public function getDisplayUrl() { + $ret = NULL; + if( !empty( $this ) && $this->isValid() ) { + $ret = static::getDisplayUrlFromHash( $this->mInfo ); + } + return $ret; + } + + /** + * Returns the create/edit url to a piece of content + * @param number $pContentId a valid content id + * @param array $pMixed a hash of params to add to the url + */ + function getEditUrl( $pContentId = NULL, $pMixed = NULL ){ + global $gLibertySystem; + $package = $gLibertySystem->mContentTypes[$this->mType['content_type_guid']]['handler_package']; + + $pathConst = strtoupper( $package ).'_PKG_URL'; + if( defined( $pathConst ) ) { + $packagePath = constant( $pathConst ); + }else{ + $packagePath = BIT_ROOT_URL.$package."/"; + } + + if( @BitBase::verifyId( $pContentId ) ) { + $ret = $packagePath.'edit.php?content_id='.$pContentId; + } elseif( $this->isValid() ) { + $ret = $packagePath.'edit.php?content_id='.$this->mContentId; + } else { + $ret = $packagePath.'edit.php'.(!empty( $pMixed )?"?":""); + } + foreach( $pMixed as $key => $value ){ + if( $key != "content_id" || ( $key == "content_id" && @BitBase::verifyId( $value ) ) ) { + $ret .= (isset($amp)?"&":"").$key."=".$value; + } + $amp = TRUE; + } + return $ret; + } + + + /** + * Not-so-pure virtual function that returns Request_URI to the preview. + * @param string Text for DisplayLink function + * @param array different possibilities depending on derived class + * @return string Formated URL address to display the page. + */ + function getPreviewUrl( $pContentId = NULL, $pMixed = NULL ) { + if( @BitBase::verifyId( $pContentId ) ) { + $ret = LIBERTY_PKG_URL.'preview.php?content_id='.$pContentId; + } elseif( @BitBase::verifyId( $pMixed['content_id'] ) ) { + $ret = LIBERTY_PKG_URL.'preview.php?content_id='.$pMixed['content_id']; + } elseif( $this->isValid() ) { + $ret = LIBERTY_PKG_URL.'preview.php?content_id='.$this->mContentId; + } else { + $ret = '#'; + } + return $ret; + } + + + /** + * Not-so-pure virtual function that returns Request_URI to a content's thumbnail representation. It is up to the derived content what exactly this means + * If not implemented in the content's class, this class will return NULL, which is an acceptable case meaning no thumbnail is available. + * FisheyeGallery, BitUser might return pictures, BitArticle might return the article topic image, etc. + * @param string Size of the url to return - should be a standard thumbnail size such as 'icon', 'avatar', 'small', 'medium', or 'large' + * @param int optional contentId tp generate the thumbnail, if empty, the mContentId variable should be used + * @param int optional secondary id, such as user_id or products_id, etc + * @return string Formated URL address to display the page. + */ + public function getThumbnailUrl( $pSize = 'small', $pSecondaryId = NULL, $pDefault=TRUE ) { + if( $this->isValid() ) { + return $this->getThumbnailUrlFromHash( $this->mInfo, $pSize ); + } + } + + + public static function getThumbnailUrlFromHash( &$pMixed, $pSize = 'small', $pSecondaryId = NULL, $pDefault=TRUE ) { + $ret = ''; + if( !empty( $pMixed['content_type']['handler_package'] ) ) { + $pkgName = $pMixed['content_type']['handler_package']; + if( $pkgPath = constant( strtoupper( $pkgName ).'_PKG_PATH' ) ) { + if( file_exists( $pkgPath.'icons/pkg_'.$pkgName.'.png' ) ) { + $ret = constant( strtoupper( $pkgName ).'_PKG_URL' ).'icons/pkg_'.$pkgName.'.png'; + } + } + } + return $ret; + } + + + public function getThumbnailUri( $pSize='small' ) { + if( $this->isValid() ) { + return $this->getThumbnailUriFromHash( $this->mInfo, $pSize ); + } + } + + public static function getThumbnailUriFromHash( &$pMixed, $pSize='small' ) { + $ret = static::getThumbnailUrlFromHash( $pMixed, $pSize ); + // Check to make sure we don't have an absolute URI already, which could be the case for custom classes + if( strpos( $ret, 'http' ) !== 0 ) { + $ret = STORAGE_HOST_URI.substr( $ret, strlen( BIT_ROOT_URL ) ); + } + return( $ret ); + } + + + public function getThumbnailFile( $pSize='small' ) { + if( $this->isValid() ) { + return $this->getThumbnailFileFromHash( $this->mInfo, $pSize ); + } + } + + public static function getThumbnailFileFromHash( &$pMixed, $pSize='small' ) { + $ret = static::getThumbnailUrlFromHash( $pMixed, $pSize ); + // Check to make sure we don't have an absolute URI already, which could be the case for custom classes + if( strpos( $ret, 'http' ) !== 0 ) { + $ret = substr( $ret, strlen( BIT_ROOT_URL ) ); + } + return( BIT_ROOT_PATH.$ret ); + } + + + /** + * Validate inbound sort_mode parameter + * @param pParamHash hash of parameters for any getList() function + * @return the link to display the page. + */ + public static function getSortModeFields() { + return array( + 'content_id', + 'modifier_user', + 'modifier_real_name', + 'creator_user', + 'creator_real_name', + 'title', + 'content_type_guid', + 'ip', + 'last_modified', + 'created', + ); + } + + /** + * Validate inbound sort_mode parameter + * @param pParamHash hash of parameters for any getList() function + * @return the link to display the page. + */ + public function convertSortMode( &$pSortMode, $pDefault='last_modified_desc' ) { + + $sortHash = static::getSortModeFields(); + + $baseSortMode = str_replace( '_asc', '', str_replace( '_desc', '', $pSortMode ) ); + + $baseSortMode = preg_replace( '/^.*\./', '', $baseSortMode ); + + if( !in_array( $baseSortMode, $sortHash ) ) { + $pSortMode = $pDefault; + } + + return $this->mDb->convertSortmode( $pSortMode ); + } + + + /** + * Liberty override to stuff content_status_id and prepares parameters with default values for any getList function + * @param pParamHash hash of parameters for any getList() function + * @return the link to display the page. + */ + public static function prepGetList( &$pListHash ) { + global $gBitUser; + if( $gBitUser->isAdmin() ) { + $pListHash['min_content_status_id'] = -9999; + } elseif( !empty( $this ) && is_object( $this ) && $this->hasAdminPermission() ) { + $pListHash['min_content_status_id'] = -999; + } elseif( !empty( $this ) && is_object( $this ) && $this->hasUpdatePermission() ) { + $pListHash['min_content_status_id'] = -99; + } else { + $pListHash['min_content_status_id'] = 1; + } + + if( empty( $pListHash['query_cache_time'] ) ) { + $pListHash['query_cache_time'] = 0; + } + + // if sort_mode is not set then use last_modified_desc + if( !empty( $pListHash['sort_mode'] )) { + if( is_string( $pListHash['sort_mode'] ) && strpos( $pListHash['sort_mode'], 'hits_' ) === 0 ) { + // if sort mode is hits_*, then assume liberty content + $pListHash['sort_mode'] = 'lch.'.$pListHash['sort_mode']; + } elseif( is_array( $pListHash['sort_mode'] )) { + foreach( $pListHash['sort_mode'] as $key => $mode ) { + if( strpos( $mode, 'hits_' ) === 0 ) { + $pListHash['sort_mode'][$key] = 'lch.'.$mode; + } + } + } + } else { + // if sort_mode is not set then use last_modified_desc + $pListHash['sort_mode'] = 'last_modified_desc'; + } + + // Users without permission can only see their own content listing + if( $gBitUser->isRegistered() && !$gBitUser->hasPermission( 'p_liberty_list_content' ) ) { + $pListHash['user_id'] = $gBitUser->mUserId; + } + + return parent::prepGetList( $pListHash ); + } + + /** + * Get a list of users who have created entries in the content table + * + * @param array hash of parameters ( content_type_guid will limit list to a single content type + * @return - none the hash is updated via the reference + **/ + function getAuthorList( &$pListHash ) { + $ret = NULL; + $mid = ''; + + $bindVars = array(); + if( !empty( $pListHash['content_type_guid'] ) ) { + $mid .= ' AND lc.`content_type_guid`=? '; + $bindVars[] = $pListHash['content_type_guid']; + } + + LibertyContent::prepGetList( $pListHash ); + $query = "SELECT DISTINCT(uu.`user_id`) AS hash_key, uu.`user_id`, SUM( lch.`hits` ) AS `ag_hits`, uu.`login`, uu.`real_name` + FROM `".BIT_DB_PREFIX."liberty_content` lc INNER JOIN `".BIT_DB_PREFIX."users_users` uu ON( uu.`user_id`=lc.`user_id` ) + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_hits` lch + ON (`lc`.`content_id` = `lch`.`content_id`) + WHERE uu.`user_id` != ".ANONYMOUS_USER_ID." AND lch.`hits` > 0 $mid + GROUP BY uu.`user_id`, uu.`login`, uu.`real_name` + ORDER BY `ag_hits` DESC"; + $result = $this->mDb->query( $query, $bindVars, $pListHash['max_records'], $pListHash['offset'] ); + while( $aux = $result->fetchRow() ) { + $ret[] = $aux; + } + return $ret; + } + + /** + * Get a list of content ranked by certain criteria set in $pListHash['sort_mode'] + * + * @param array hash of parameters ( content_type_guid will limit list to a single content type + * @return - data + **/ + function getContentRanking( $pListHash ) { + $pListHash['sort_mode'] = !empty( $pListHash['sort_mode'] ) ? $pListHash['sort_mode'] : 'hits_desc'; + + if( $pListHash['sort_mode'] == 'top_authors' ) { + global $gBitUser; + $ret['data'] = $gBitUser->getAuthorList( $pListHash ); + } else { + include_once( LIBERTY_PKG_CLASS_PATH.'LibertyContent.php' ); + $libertyContent = new LibertyContent(); + $ret['data'] = $libertyContent->getContentList( $pListHash ); + } + + $ret['title'] = !empty( $pListHash['title'] ) ? $pListHash['title'] : tra( "Content Ranking" ); + $ret['attribute'] = !empty( $pListHash['attribute'] ) ? $pListHash['attribute'] : tra( "Hits" ); + + return $ret; + } + + /** + * Get a list of all content + * + * @param string $pListHash['content_type_guid'] Content GUID to limit the list to + * @param integer $pListHash['max_records'] Number of the first record to access ( used to page the list ) + * @param integer $pListHash['offset'] Number of records to return + * @param string $pListHash['sort_mode'] Name of the field to sort by ( extended by _asc or _desc for sort direction ) + * @param array $pListHash['find'] List of text elements to filter the results by + * @param integer $pListHash[''] User ID - If set, then only the objcets created by that user will be returned + * $pListHash['last_modified'] date - modified since + * $pListHash['end_date'] date - modified before + * @return array An array of mInfo type arrays of content objects + **/ + function getContentList( &$pListHash ) { + global $gLibertySystem, $gBitSystem, $gBitUser, $gBitSmarty; + + LibertyContent::prepGetList( $pListHash ); + + $hashSql = array('select'=>array(), 'join'=>array(),'where'=>array() ); + $hashBindVars = array('select'=>array(), 'where'=>array(), 'join'=>array()); + if( !empty( $pListHash['content_type_guid'] ) && is_array( $pListHash['content_type_guid'] )) { + foreach( $pListHash['content_type_guid'] as $contentTypeGuid ) { + $this->getFilter( $contentTypeGuid, $hashSql, $hashBindVars, $pListHash ); + } + } elseif( !empty( $pListHash['content_type_guid'] )) { + $this->getFilter( $pListHash['content_type_guid'], $hashSql, $hashBindVars, $pListHash ); + } + + if( !empty( $hashSql['select'] )) { + $selectSql = ','.implode( ',', $hashSql['select'] ); + } else { + $selectSql = ''; + } + $joinSql = implode( ' ', $hashSql['join'] ); + $whereSql = ''; + if( empty( $hashBindVars['join'] )) { + $bindVars = array(); + } else { + $bindVars = $hashBindVars['join']; + } + $this->getServicesSql( 'content_list_sql_function', $selectSql, $joinSql, $whereSql, $bindVars, NULL, $pListHash ); + + if( $pListHash['sort_mode'] == 'size_desc' ) { + $pListHash['sort_mode'] = 'wiki_page_size_desc'; + } + + if( $pListHash['sort_mode'] == 'size_asc' ) { + $pListHash['sort_mode'] = 'wiki_page_size_asc'; + } + + $old_sort_mode = ''; + + $sortHash = array( + 'versions_desc', + 'versions_asc', + 'links_asc', + 'links_desc', + 'backlinks_asc', + 'backlinks_desc' + ); + + if( in_array( $pListHash['sort_mode'], $sortHash ) ) { + $old_offset = $pListHash['offset']; + $old_max_records = $pListHash['max_records']; + $old_sort_mode = $pListHash['sort_mode']; + $pListHash['sort_mode'] = 'modifier_user_desc'; + $pListHash['offset'] = 0; + $pListHash['max_records'] = -1; + } + + if( is_array( $pListHash['find'] ) ) { // you can use an array of titles + $whereSql .= " AND lc.`title` IN ( ".implode( ',',array_fill( 0,count( $pListHash['find'] ),'?' ) ).") "; + $bindVars = array_merge( $pListHash['find'], $pListHash['find'] ); + } elseif( !empty( $pListHash['find'] ) && is_string( $pListHash['find'] ) ) { // or a string + $whereSql .= " AND UPPER(lc.`title`) like ? "; + $bindVars[] = ( '%' . strtoupper( $pListHash['find'] ) . '%' ); + } + + if( !empty( $pListHash['content_id_list'] ) ) { // you can use an array of titles + $whereSql .= " AND lc.`content_id` IN ( ".implode( ',',array_fill( 0,count( $pListHash['content_id_list'] ),'?' ) ).") "; + $bindVars = array_merge( $bindVars, $pListHash['content_id_list'] ); + } + + // this is necessary to display useful information in the liberty RSS feed + if( !empty( $pListHash['include_data'] ) ) { + $selectSql .= ", lc.`data`, lc.`format_guid`"; + } + + // if we want the primary attachment for each object + if( $gBitSystem->isFeatureActive( 'liberty_display_primary_attach' ) ){ + $selectSql .= ', lfp.`file_name`, lfp.`mime_type`, la.`attachment_id`, '; + $joinSql .= "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` lfp ON( lfp.`file_id` = la.`foreign_id` )"; + } + + // Allow selection based on arbitrary time limits -- used in calendar + // TODO: We should replace usages of from_date and until_date with this generic setup and depricate those + if( !empty( $pListHash['time_limit_column'] )) { + if( empty( $pListHash['time_limit_table'] ) ) { + $pListHash['time_limit_table'] = 'lc.'; + } + if( !empty( $pListHash['time_limit_start'] ) ) { + $whereSql .= " AND ".$pListHash['time_limit_table']."`".$pListHash['time_limit_column']."` >= ? "; + $bindVars[] = $pListHash['time_limit_start']; + } + if( !empty( $pListHash['time_limit_stop'] ) ) { + $whereSql .= " AND ".$pListHash['time_limit_table']."`".$pListHash['time_limit_column']."` <= ? "; + $bindVars[] = $pListHash['time_limit_stop']; + } + } + + if( @$this->verifyId( $pListHash['user_id'] ) ) { + $whereSql .= " AND lc.`user_id` = ? "; + $bindVars[] = $pListHash['user_id']; + } + + if( @$this->verifyId( $pListHash['link_content_id'] ) ){ + $joinSql .= " INNER JOIN `".BIT_DB_PREFIX."liberty_content_links` lclk ON ( lc.`content_id` = lclk.`to_content_id` )"; + $whereSql .= " AND lclk.`from_content_id` = ? "; + $bindVars[] = (int)$pListHash['link_content_id']; + } + + if( $gBitSystem->isFeatureActive( 'liberty_display_status' ) && $gBitUser->hasPermission( 'p_liberty_view_all_status' )) { + $selectSql .= ", lcs.`content_status_id`, lcs.`content_status_name`"; + $joinSql .= " LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_status` lcs ON ( lc.`content_status_id` = lcs.`content_status_id` )"; + if( !empty( $pListHash['content_status_id'] )) { + if( $pListHash['content_status_id'] == 'not_available' ) { + $whereSql .= " AND lcs.`content_status_id` <> ? "; + $bindVars[] = 50; + } else { + $whereSql .= " AND lcs.`content_status_id` = ? "; + $bindVars[] = (int)$pListHash['content_status_id']; + } + } + } + + // join on specific content_type_guids + if( !empty( $pListHash['content_type_guid'] ) && is_string( $pListHash['content_type_guid'] ) ) { + $whereSql .= ' AND lc.`content_type_guid`=? '; + $bindVars[] = $pListHash['content_type_guid']; + } elseif( !empty( $pListHash['content_type_guid'] ) && is_array( $pListHash['content_type_guid'] ) ) { + $whereSql .= " AND lc.`content_type_guid` IN ( ".implode( ',',array_fill ( 0, count( $pListHash['content_type_guid'] ),'?' ) )." )"; + $bindVars = array_merge( $bindVars, $pListHash['content_type_guid'] ); + } + + // exclude by content_type_guids + if( !empty( $pListHash['exclude_content_type_guid'] ) && is_string( $pListHash['exclude_content_type_guid'] ) ) { + $whereSql .= " AND lc.`content_type_guid` != ?"; + $bindVars[] = $pListHash['exclude_content_type_guid']; + } elseif( !empty( $pListHash['exclude_content_type_guid'] ) && is_array( $pListHash['exclude_content_type_guid'] ) ) { + $whereSql .= " AND lc.`content_type_guid` NOT IN ( ".implode( ',',array_fill ( 0, count( $pListHash['exclude_content_type_guid'] ),'?' ) )." )"; + $bindVars = array_merge( $bindVars, $pListHash['exclude_content_type_guid'] ); + } + + // only display content modified more recently than this (UTC timestamp) + if( !empty( $pListHash['from_date'] ) ) { + $whereSql .= ' AND lc.`last_modified` >= ?'; + $bindVars[] = $pListHash['from_date']; + } + + // only display content modified before this (UTC timestamp) + if( !empty( $pListHash['until_date'] ) ) { + $whereSql .= ' AND lc.`last_modified` <= ?'; + $bindVars[] = $pListHash['until_date']; + } + + // Should results be hashed or sequential indexed + $hashKeySql = ''; + if( !empty( $pListHash['hash_key'] ) ) { + $hashKeySql = $pListHash['hash_key'].' AS `hash_key`, '; + } + + if( $gBitSystem->isPackageActive( 'gatekeeper' ) ) { + if( $gBitSystem->isPackageActive( 'fisheye' ) ) { + // This is really ugly to have in here, and really would be better off somewhere else. + // However, because of the specific nature of the current implementation of fisheye galleries, I am afraid + // this is the only place it can go to properly enforce gatekeeper protections. Hopefully a new content generic + // solution will be available in ReleaseTwo - spiderr + if( $this->mDb->isAdvancedPostgresEnabled() ) { +// $joinSql .= " LEFT OUTER JOIN `".BIT_DB_PREFIX."fisheye_gallery_image_map` fgim ON (fgim.`item_content_id`=lc.`content_id`)"; + $whereSql .= " AND (SELECT ls.`security_id` FROM connectby('fisheye_gallery_image_map', 'gallery_content_id', 'item_content_id', 'item_content_id', text( lc.`content_id` ), 0, '/') AS t(`cb_gallery_content_id` int, `cb_item_content_id` int, level int, branch text, pos int), `".BIT_DB_PREFIX."gatekeeper_security_map` cgm, `".BIT_DB_PREFIX."gatekeeper_security` ls + WHERE ls.`security_id`=cgm.`security_id` AND cgm.`content_id`=`cb_gallery_content_id` LIMIT 1) IS NULL"; + } + } + } + + $sortHash = array( + 'content_id_desc', + 'content_id_asc', + 'modifier_user_desc', + 'modifier_user_asc', + 'modifier_real_name_desc', + 'modifier_real_name_asc', + 'creator_user_desc', + 'creator_user_asc', + 'creator_real_name_desc', + 'creator_real_name_asc', + ); + + if( in_array( $pListHash['sort_mode'], $sortHash ) ) { + $orderTable = ''; + } elseif( !empty( $pListHash['order_table'] ) ) { + $orderTable = $pListHash['order_table']; + } elseif( !empty( $pListHash['sort_mode'] ) && strtolower( substr( $pListHash['sort_mode'], 0, 4 ) ) =='hits' ) { + $orderTable = 'lch.'; + } elseif( strpos( $pListHash['sort_mode'], '.' ) ) { + // do not specifiy orderTable of sort_mode already has a . in it + $orderTable = ''; + } else { + $orderTable = 'lc.'; + } + + if (!empty($hashSql['where'])) { + $whereSql .= ' AND '.implode(' ', $hashSql['where']); + } + if (!empty($hashBindVars['where'])) { + $bindVars = array_merge($bindVars, $hashBindVars['where']); + } + + $whereSql = preg_replace( '/^[\s]*AND\b/i', 'WHERE ', $whereSql ); + + // If sort mode is versions then offset is 0, max_records is -1 (again) and sort_mode is nil + // If sort mode is links then offset is 0, max_records is -1 (again) and sort_mode is nil + // If sort mode is backlinks then offset is 0, max_records is -1 (again) and sort_mode is nil + $query = " + SELECT + $hashKeySql + uue.`login` AS `modifier_user`, + uue.`real_name` AS `modifier_real_name`, + uue.`user_id` AS `modifier_user_id`, + uuc.`login` AS `creator_user`, + uuc.`real_name` AS `creator_real_name`, + uuc.`user_id` AS `creator_user_id`, + lch.`hits`, + lch.`last_hit`, + lc.`event_time`, + lc.`title`, + lc.`last_modified`, + lc.`content_type_guid`, + lc.`ip`, + lc.`created`, + lc.`content_id`, + lcds.`data` AS `summary` + $selectSql + FROM `".BIT_DB_PREFIX."liberty_content` lc + INNER JOIN `".BIT_DB_PREFIX."users_users` uuc ON (lc.`user_id`=uuc.`user_id`) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_users` uue ON (lc.`modifier_user_id`=uue.`user_id`) + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_hits` lch ON( lc.`content_id` = lch.`content_id`) + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_data` lcds ON (lc.`content_id` = lcds.`content_id` AND lcds.`data_type`='summary') + $joinSql + $whereSql + ORDER BY ".$orderTable.$this->convertSortMode($pListHash['sort_mode']); + + $query_cant = " + SELECT + COUNT(lc.`content_id`) + FROM `".BIT_DB_PREFIX."liberty_content` lc + $joinSql + $whereSql"; + + $cant = $this->mDb->getOne( $query_cant, $bindVars ); + $pListHash["cant"] = $cant; + + # Check for offset out of range + if( $pListHash['offset'] < 0 ) { + $pListHash['offset'] = 0; + } elseif ( $pListHash['offset'] > $pListHash["cant"] ) { + $lastPageNumber = ceil ( $pListHash["cant"] / $pListHash['max_records'] ) - 1; + $pListHash['offset'] = $pListHash['max_records'] * $lastPageNumber; + } + + + if( !empty( $hashBindVars['select'] ) ) { + $bindVars = array_merge($hashBindVars['select'], $bindVars); + } + $result = $this->mDb->query( $query, $bindVars, $pListHash['max_records'], $pListHash['offset'] ); + + $ret = array(); + $contentTypes = $gLibertySystem->mContentTypes; + while( $aux = $result->fetchRow() ) { + if( !empty( $contentTypes[$aux['content_type_guid']] ) ) { + // quick alias for code readability + $type = &$contentTypes[$aux['content_type_guid']]; + $aux['content_name'] = $type['content_name']; + $aux['creator'] = (isset( $aux['creator_real_name'] ) ? $aux['creator_real_name'] : $aux['creator_user'] ); + $aux['real_name'] = (isset( $aux['creator_real_name'] ) ? $aux['creator_real_name'] : $aux['creator_user'] ); + $aux['editor'] = (isset( $aux['modifier_real_name'] ) ? $aux['modifier_real_name'] : $aux['modifier_user'] ); + $aux['user'] = $aux['creator_user']; + $aux['user_id'] = $aux['creator_user_id']; + if( !empty( $gBitSystem->mPackages[$type['handler_package']] ) ) { + if( !class_exists( $type['handler_class'] ) ) { + $gLibertySystem->getContentClassName( $aux['content_type_guid'] ); + } + if( class_exists( $type['handler_class'] ) ) { + if( $aux['content_type_guid'] == BITUSER_CONTENT_TYPE_GUID ) { + // here we provide getDisplay(Link|Url) with user-specific information that we get the correct links to display in pages + $userInfo = $gBitUser->getUserInfo( array( 'content_id' => $aux['content_id'] )); + $aux['title'] = $type['handler_class']::getTitleFromHash( $userInfo ); + $aux['display_link'] = $type['handler_class']::getDisplayLinkFromHash( $userInfo, $userInfo['login'] ); + $aux['display_url'] = $type['handler_class']::getDisplayUrlFromHash( $userInfo ); + } else { + $aux['title'] = $type['handler_class']::getTitleFromHash( $aux ); + $aux['display_link'] = $type['handler_class']::getDisplayLinkFromHash( $aux, $aux['title'] ); + /** + * @TODO standardize getDisplayUrl params + * nice try, but you can't do this because individual classes have gone off the reservation changing the params they accept + * for distributed packages we need to enforce that method overrides all take the same basic params. + **/ + // $aux['display_url'] = $type['content_object']->getDisplayUrl( NULL, $aux ); + $aux['display_url'] = BIT_ROOT_URL."index.php?content_id=".$aux['content_id']; + } + } + + if( !empty( $pListHash['thumbnail_size'] ) ) { + $aux['content_object'] = static::getLibertyObject( $aux['content_id'], $aux['content_type_guid'] ); + if( $aux['content_object']->load( FALSE ) ) { + $aux['thumbnail_url'] = $aux['content_object']->getThumbnailUrl( $pListHash['thumbnail_size'] ); + } + } + + } + + /** + * @TODO standardize use of thumbnail_url and provision for hash of thumbnail sizes + * + * We have a bit of a mess with the use of thumbnail_url where sometimes it is a hash of sizes, and sometimes it is a single size + * we should standardize the param and what kind of value it returns, and if we need both types then have two params. + * This ultimately might need to be more sophisticated to deal with different mime types. + **/ + if( $gBitSystem->isFeatureActive( 'liberty_display_primary_attach' ) ) { + $aux['thumbnail_urls'] = liberty_fetch_thumbnails( $aux ); + } + + if( isset( $aux['hash_key'] ) ) { + $ret[$aux['hash_key']] = $aux; + } else { + $ret[] = $aux; + } + } + } + + // If sortmode is versions, links or backlinks sort using the ad-hoc function and reduce using old_offse and old_max_records + if( $old_sort_mode == 'versions_asc' && !empty( $ret['versions'] ) ) { + usort( $ret, 'compare_versions' ); + } + + if( $old_sort_mode == 'versions_desc' && !empty( $ret['versions'] ) ) { + usort( $ret, 'r_compare_versions' ); + } + + if( $old_sort_mode == 'links_desc' && !empty( $ret['links'] ) ) { + usort( $ret, 'compare_links' ); + } + + if( $old_sort_mode == 'links_asc' && !empty( $ret['links'] ) ) { + usort( $ret, 'r_compare_links' ); + } + + if( $old_sort_mode == 'backlinks_desc' && !empty( $ret['backlinks'] ) ) { + usort( $ret, 'compare_backlinks' ); + } + + if( $old_sort_mode == 'backlinks_asc' && !empty( $ret['backlinks'] ) ) { + usort( $ret, 'r_compare_backlinks' ); + } + + if( in_array( $old_sort_mode, array( + 'versions_desc', + 'versions_asc', + 'links_asc', + 'links_desc', + 'backlinks_asc', + 'backlinks_desc' + ))) { + $ret = array_slice( $ret, $old_offset, $old_max_records ); + } + + LibertyContent::postGetList( $pListHash ); + return $ret; + } + + /** + * Get a list of all structures this content is a member of + **/ + function getStructures() { + $ret = array(); + if( $this->isValid() ) { + $structures_added = array(); + $query = 'SELECT ls.*, lc.`title`, tcr.`title` AS `root_title` + FROM `'.BIT_DB_PREFIX.'liberty_content` lc, `'.BIT_DB_PREFIX.'liberty_structures` ls + INNER JOIN `'.BIT_DB_PREFIX.'liberty_structures` tsr ON( tsr.`structure_id`=ls.`root_structure_id` ) + INNER JOIN `'.BIT_DB_PREFIX.'liberty_content` tcr ON( tsr.`content_id`=tcr.`content_id` ) + WHERE lc.`content_id`=ls.`content_id` AND ls.`content_id`=?'; + if( $result = $this->mDb->query( $query,array( $this->mContentId ) ) ) { + while ($res = $result->fetchRow()) { + $ret[] = $res; + } + } + } + return $ret; + } + + /* + * Splits content either at the ...split... or at the + * length specified if no manual split is in the content. + * + * @param pParseHash a hash with 'data' in it and any + * arguments to the parser as required + * @param pLength the length to split at if no ...split... is present + * @param pForceLength force split at length (default false) + * @return parsed data cut at LIBERTY_SPLIT_REGEX or at $pLength + */ + function parseSplit( $pParseHash, $pLength = 500, $pForceLength = FALSE ) { + global $gLibertySystem, $gBitSystem; + + if( $pForceLength ) { + $res['data'] = preg_replace( LIBERTY_SPLIT_REGEX, '', $res['data'] ); + } + + // Indicate that we are parsing split data. This will clean up the HTML better and avoid pre / post filters + $pParseHash['split_parse'] = TRUE; + + // copy data that we can compare strings later on + $res['data'] = $pParseHash['data']; + + // allways set the cache extension to description if it's not set manually + $pParseHash['cache_extension'] = !empty( $pParseHash['cache_extension'] ) ? $pParseHash['cache_extension'] : 'desc'; + + // split data according to user specifications + if( preg_match( LIBERTY_SPLIT_REGEX, $res['data'] )) { + // this has been manually split + $res['man_split'] = TRUE; + $parts = preg_split( LIBERTY_SPLIT_REGEX, $res['data'] ); + $pParseHash['data'] = $parts[0]; + } else { + // Include length in cache file + $pParseHash['cache_extension'] .= '.'.$pLength; + $pParseHash['data'] = substr( $res['data'], 0, $pLength ); + // snip off a broken tag at the end if there is one + $pParseHash['data'] = preg_replace( '!<[a-zA-Z/][^>]*?$!', '', $pParseHash['data'] ); + } + + // set 'has_more' and remove cache_extension if we don't need it + if( !( $res['has_more'] = ( $res['data'] != $pParseHash['data'] ))) { + $pParseHash['cache_extension'] = NULL; + } + + if( !empty( $pParseHash['data'] )) { + // parse data and run it through postsplit filter + if( $parsed = self::parseDataHash( $pParseHash, $this )) { + // parsing split content can break stuff so we remove trailing junk + $res['parsed'] = $res['parsed_description'] = preg_replace( '!((<br\b[^>]*>)*\s*)*$!si', '', $parsed ); + + // we append '...' when the split was generated automagically + if( empty( $res['man_split'] ) && !empty( $res['has_more'] )) { + $res['parsed_description'] .= '…'; + } + } + } else { + // did we parse an empty page? + $res['parsed'] = $res['parsed_description'] = ''; + $res['has_more'] = FALSE; + } + + return $res; + } + + public function getParsedData() { + if( empty( $this->mInfo['parsed_data'] ) ) { + $this->parseData(); + } + return $this->mInfo['parsed_data']; + } + + protected function parseData() { + // get the data into place + $this->mInfo['parsed_data'] = self::parseDataHash( $this->mInfo, $this ); + } + + /** + * Process the raw content blob using the speified content GUID processor + * + * This is the "object like" method. It should be more object like, + * but for now, we'll just point to the old lib style "parse_data" - XOXO spiderr + * @param pMixed can be a string or a hash - if a string is given, it will be parsed without the use of cache + * @param string pMixed['data'] string to be parsed + * @param int pMixed['content_id'] content_id or the item to be parsed - required for caching and optimal parser performance + * @param boolean pMixed['no_cache'] disable caching + * @param string pMixed['cache_extension'] cache to a separate file. useful for truncated displays of parsed content such as article front page + * @param string pFormatGuid processor to use + * @return string Formated data string + */ + public static function parseDataHash( &$pParseHash, $pObject=NULL ) { + global $gLibertySystem, $gBitSystem, $gBitUser; +/* + if( !is_array( $pParseHash ) ) { + $parseHash['data'] = $pMixed; + } elseif( empty( $pParseHash['data'] ) ) { + $pParseHash['data'] = ''; + } +*/ + // sanitise pParseHash a bit + $pParseHash['content_id'] = !empty( $pParseHash['content_id'] ) ? $pParseHash['content_id'] : NULL; + $pParseHash['cache_extension'] = !empty( $pParseHash['cache_extension'] ) ? $pParseHash['cache_extension'] : NULL; + $pParseHash['user_id'] = !empty( $pParseHash['user_id'] ) ? $pParseHash['user_id'] : is_object( $gBitUser ) ? $gBitUser->mUserId : ANONYMOUS_USER_ID; + + // Ensure we have a format + if( empty( $pParseHash['format_guid'] ) ) { + // use system wide default + $pParseHash['format_guid'] = $gBitSystem->getConfig( 'default_format', 'tikiwiki' ); + if( is_a( $pObject, 'LibertyContent' ) && ($objectFormat = $pObject->getField( 'format_guid' ) ) ) { + // if pObject has a specified format, use that... + $pParseHash['format_guid'] = $objectFormat; + } + } + + $ret = NULL; + // Handle caching if it is enabled. + if( $gBitSystem->isFeatureActive( 'liberty_cache' ) && !empty( $pParseHash['content_id'] ) && empty( $pParseHash['no_cache'] ) ) { + if( $cacheFile = LibertyContent::getCacheFile( $pParseHash['content_id'], $pParseHash['cache_extension'] ) ) { + // Attempt to read cache file + if( !( $ret = LibertyContent::readCacheFile( $cacheFile ))) { + // failed to read from cache. + $parseAndCache = TRUE; + } else { + // Note that we read from cache. + $pParseHash['is_cached'] = TRUE; + } + } + } + + // if $ret is empty, we haven't read anything from cache yet - we need to parse the raw data + if( empty( $ret ) || !empty( $parseAndCache )) { + if( !empty( $pParseHash['data'] ) && $pParseHash['format_guid'] ) { + $replace = array(); + // extract and protect ~pp~...~/pp~ and ~np~...~/np~ sections + parse_protect( $pParseHash['data'], $replace ); + + // some few filters such as stencils need to be before the data plugins + self::filterDataHash( $pParseHash['data'], $pParseHash, 'preplugin' ); + + // handle all liberty data plugins like {code} and {attachment} usage in all formats + parse_data_plugins( $pParseHash['data'], $replace, $pObject, $pParseHash ); + + // pre parse filter according to what we're parsing - split or full body + $filter = empty( $pParseHash['split_parse'] ) ? 'parse' : 'split'; + self::filterDataHash( $pParseHash['data'], $pParseHash, 'pre'.$filter ); + + if( $func = $gLibertySystem->getPluginFunction( $pParseHash['format_guid'], 'load_function' ) ) { + // get the beast parsed + if( $ret = $func( $pParseHash, $pObject ) ) { + // post parse filter + self::filterDataHash( $ret, $pParseHash, 'post'.$filter ); + + // before we cache we insert the protected sections back - even after the filters. + // might not be ideal but it allows stuff like ~pp~{maketoc}~/pp~ + $replace = array_reverse( $replace ); + foreach( $replace as $rep ) { + $ret = str_replace( $rep["key"], $rep["data"], $ret ); + } + + if( !empty( $parseAndCache )) { + LibertyContent::writeCacheFile( $cacheFile, $ret ); + } + } + } + } + } + + return $ret; + } + + protected function filterData( &$pData, &$pFilterHash, $pFilterStage = 'preparse' ) { + self::filterDataHash( $pData, $pFilterHash, $pFilterStage, $this ); + } + + /** + * filterData will apply one of the specified filter stages to the input data + * + * @param array $pFilterHash array of data that should be filtered + * @param string $pFilterHash[data] is the actual data that needs to be filtered + * @param keyword $pFilterStage specify what filter stage the data is at: pre, post, presplit or postsplit + * @access public + * @return filtered data + */ + public static function filterDataHash( &$pData, &$pFilterHash, $pFilterStage = 'preparse', $pObject = NULL ) { + global $gLibertySystem; + if( !empty( $pData ) && ($filters = $gLibertySystem->getPluginsOfType( FILTER_PLUGIN )) ) { + foreach( $filters as $guid => $filter ) { +//vvd( $guid, $gLibertySystem->isPluginActive( $guid ), $pFilterStage, $gLibertySystem->getPluginFunction( $guid, $pFilterStage.'_function' ));//, $pData ); //, $pFilterHash, $pObject ); + if( $gLibertySystem->isPluginActive( $guid ) && ($func = $gLibertySystem->getPluginFunction( $guid, $pFilterStage.'_function' )) ) { + $func( $pData, $pFilterHash, $pObject ); + } + } + } + } + + /** + * Special parsing for multipage articles + * + * Temporarily remove <pre>...</pre> sections to protect + * from broke <pre>pre</pre> tags and leave well known <pre>pre</pre> + * behaviour (i.e. type all text inside AS IS w/o + * any interpretation) + * @param string Data to process + * @return string Extracted pages + */ + function getNumberOfPages( &$data ) { + $preparsed = array(); + + preg_match_all("/(<[Pp][Rr][Ee]>)((.|\n)*?)(<\/[Pp][Rr][Ee]>)/", $data, $preparse); + $idx = 0; + + foreach (array_unique($preparse[2])as $pp) { + $key = md5(BitSystem::genPass()); + + $aux["key"] = $key; + $aux["data"] = $pp; + $preparsed[] = $aux; + $data = str_replace($preparse[1][$idx] . $pp . $preparse[4][$idx], $key, $data); + $idx = $idx + 1; + } + + $parts = explode(defined('PAGE_SEP') ? PAGE_SEP : "...page...", $data); + return count($parts); + } + + /** + * Special parsing for a particular page of a multipage article + * + * Temporary remove <PRE></PRE> secions to protect + * from broke <PRE> tags and leave well known <PRE> + * behaviour (i.e. type all text inside AS IS w/o + * any interpretation) + * @param string Data to process + * @param integer Number of page to extract + * @return string Extracted page + */ + function getPage( &$data, $i ) { + $preparsed = array(); + + preg_match_all("/(<[Pp][Rr][Ee]>)((.|\n)*?)(<\/[Pp][Rr][Ee]>)/", $data, $preparse); + $idx = 0; + + foreach (array_unique($preparse[2])as $pp) { + $key = md5(BitSystem::genPass()); + + $aux["key"] = $key; + $aux["data"] = $pp; + $preparsed[] = $aux; + $data = str_replace($preparse[1][$idx] . $pp . $preparse[4][$idx], $key, $data); + $idx = $idx + 1; + } + + // Get slides + $parts = explode(defined('PAGE_SEP') ? PAGE_SEP : "...page...", $data); + + if (substr($parts[$i - 1], 1, 5) == "<br/>") { + $ret = substr($parts[$i - 1], 6); + } else { + $ret = $parts[$i - 1]; + } + + // Replace back <PRE> sections + foreach ($preparsed as $pp) { + $ret = str_replace($pp["key"], "<pre>" . $pp["data"] . "</pre>", $ret); + } + + return $ret; + } + + /** + * convenience function to process a $_REQUEST array + **/ + function decodeAjaxRequest( &$pParamHash ){ + foreach( $pParamHash as $key => $value ){ + if( is_string($value) ){ + $pParamHash[$key] = htmlspecialchars_decode( $value ); + } + } + } + + /** + * Set content related mStructureId + * + * @param integer Structure ID + */ + function setStructure( $pStructureId ) { + if( $this->verifyId( $pStructureId ) ) { + $this->mStructureId = $pStructureId; + } + } + + /** + * Check the number of structures that the content object is being used in + * + * @param integer Structure ID ( If NULL or not supplied check all structures ) + * @return integer Number of structures that this content object is located in + */ + function isInStructure( $pStructureId=NULL ) { + if( $this->isValid() ) { + $whereSql = NULL; + $bindVars = array( $this->mContentId ); + if( $pStructureId ) { + array_push( $bindVars, $pStructureId ); + $whereSql = ' AND ls.`root_structure_id`=? '; + } + $query = "SELECT `structure_id` FROM `".BIT_DB_PREFIX."liberty_structures` ls + WHERE ls.`content_id`=? $whereSql"; + $cant = $this->mDb->getOne( $query, $bindVars ); + return $cant; + } + } + + /** + * This is a generic liberty content function to gather indexable words. Override this function + * in your BitPackage.php file if you need to add more indexable words from files other than + * tiki_content and users_users. + */ + function setIndexData( $pContentId = 0 ) { + global $gBitSystem ; + if ( $pContentId == 0 ) $pContentId = $this->mContentId; + $sql = "SELECT lc.`title`, lc.`data`, lcds.`data` AS `summary`, uu.`login`, uu.`real_name` + FROM `" . BIT_DB_PREFIX . "liberty_content` lc + INNER JOIN `" . BIT_DB_PREFIX . "users_users` uu ON uu.`user_id` = lc.`user_id` + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_data` lcds ON (lc.`content_id` = lcds.`content_id` AND lcds.`data_type`='summary') + WHERE lc.`content_id` = ?" ; + $res = $gBitSystem->mDb->getRow($sql, array($pContentId)); + if (!(isset($this->mInfo['no_index']) and $this->mInfo['no_index'] == true)) { + $this->mInfo['index_data'] = $res["title"] . " " . $res["data"] . " " . $res["login"] . " " . $res["real_name"] ; + } + } + + // -------------------- Cache Funtions -------------------- // + + /** + * Check if content has a cache file + * + * @param array $pContentId Content id of cached item + * @access public + * @return absolute path + */ + public function isCached( $pContentId = NULL ) { + global $gBitSystem; + return( $gBitSystem->getConfig( 'liberty_cache' ) && is_file( LibertyContent::getCacheFile( $pContentId ))); + } + + /** + * Get the path where we store liberty cached content + * + * @access public + * @return absolute path + */ + public static function getCacheBasePath() { + return str_replace( '//', '/', TEMP_PKG_PATH.LIBERTY_PKG_NAME.'/cache/' ); + } + + /** + * Get the path to directory where an individual cache item is stored + * + * @param array $pContentId Content id of cached item + * @access public + * @return path on success, FALSE on failure + */ + public static function getCachePath( $pContentId = NULL ) { + global $gBitSystem; + + $ret = FALSE; + if( @BitBase::verifyId( $pContentId ) ) { + if( $gBitSystem->isFeatureActive( 'liberty_flat_cache' )) { + $subdir = floor( $pContentId / 1000 ); + $path = LibertyContent::getCacheBasePath().$subdir.'/'; + } else { + $subdir = $pContentId % 1000; + $path = LibertyContent::getCacheBasePath().$subdir.'/'.$pContentId.'/'; + } + if( is_dir( $path ) || mkdir_p( $path ) ) { + $ret = $path; + } + } + + return $ret; + } + + /** + * Attempts to read from the specified cache file checking if the + * cached data has expired. + * + * @param the name of the cache file from getCacheFile() + * @return the contents of the cache file or NULL + */ + public static function readCacheFile( $pCacheFile ) { + global $gBitSystem; + $ret = NULL; + if( is_file( $pCacheFile ) && ( time() - filemtime( $pCacheFile )) < $gBitSystem->getConfig('liberty_cache') && filesize( $pCacheFile ) > 0 ) { + // get contents from cache file + $h = fopen( $pCacheFile, 'r' ); + $ret = fread( $h, filesize( $pCacheFile ) ); + fclose( $h ); + } + return $ret; + } + + /** + * Unconditionally writes data to the cache file. + * Does not check for error assuming if write failed that the + * read will as well. + * + * @param the name of the cache file from getCacheFile() to write + * @param the contents to write to the file + */ + public static function writeCacheFile( $pCacheFile, $pData ) { + // Cowardly refuse to write nothing. + if( !empty( $pData )) { + // write parsed contents to cache file + $h = fopen( $pCacheFile, 'w' ); + fwrite( $h, $pData ); + fclose( $h ); + } + } + + /** + * Get the path to file where an individual cache item is stored + * + * @param array $pContentId Content id of cached item + * @access public + * @return filename on success, FALSE on failure + */ + public static function getCacheFile( $pContentId = NULL, $pCacheExtension = NULL ) { + if( $ret = LibertyContent::getCachePath( $pContentId ) ) { + return( $ret.$pContentId.( !empty( $pCacheExtension ) ? '.'.$pCacheExtension : '') ); + } else { + return FALSE; + } + } + + /** + * Delete cache files for a given content item + * + * @param array $pContentId + * @access public + * @return TRUE on success, FALSE on failure + */ + public static function expungeCacheFile( $pContentId = NULL ) { + global $gBitSystem; + if( $gBitSystem->isFeatureActive( 'liberty_cache' ) && @BitBase::verifyId( $pContentId ) ) { + // we need to unlink all files with the same id and any extension + if( $dh = opendir( $cacheDir = LibertyContent::getCachePath( $pContentId ) ) ) { + while( FALSE !== ( $file = readdir( $dh ) ) ) { + if( $file != '.' && $file != '..' && ( preg_match( "/^".$pContentId."$/", $file ) || preg_match( "/^".$pContentId."\..*/", $file ) ) ) { + @unlink( $cacheDir.$file ); + } + } + } + } + + return TRUE; + } + + /** + * Delete liberty cache + * + * @param array $pContentId + * @access public + * @return TRUE on success, FALSE on failure + */ + public static function expungeCache() { + global $gBitSystem; + $ret = FALSE; + if( $gBitSystem->isFeatureActive( 'liberty_cache' )) { + $cacheDir = LibertyContent::getCacheBasePath(); + // make sure that we're in the temp dir at least + if( strstr( $cacheDir, str_replace( '//', '/', TEMP_PKG_PATH ))) { + unlink_r( $cacheDir ); + // make sure we have a usable cache directory to work with + $ret = ( is_dir( $cacheDir ) || mkdir_p( $cacheDir )); + } + } + return $ret; + } + + /** + * getFilter + * + * @param array $pContentTypeGuid + * @param array $pSql + * @param array $pBindVars + * @param array $pHash + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + * @todo + * - i think this function is not being used and will hopefully be removed soon - xing - Saturday Jul 07, 2007 19:54:02 CEST + * - it is called in getContentList but I think that services can do what it does now - nick - Sunday Sep 30, 2007 + */ + function getFilter( $pContentTypeGuid, &$pSql, &$pBindVars, $pHash = null) { + global $gLibertySystem, $gBitSystem; + foreach ($gLibertySystem->mContentTypes as $type) { + if ($type['content_type_guid'] == $pContentTypeGuid) { + if( !empty( $gBitSystem->mPackages[$type['handler_package']]['path'] ) ) { + $path = $gBitSystem->mPackages[$type['handler_package']]['path'];//constant(strtoupper($type['handler_package']).'_PKG_PATH'); + if( file_exists( $path.$type['handler_file'] ) ) { + include_once($path.$type['handler_file']); + if ( class_exists( $type['handler_class'] ) ) { + $content = new $type['handler_class']; + if (method_exists($content, 'getFilterSql')) { + $content->getFilterSql($pSql, $pBindVars, $pHash); + } + } + } + } + } + } + } + + // -------------------- Action Logging Funtions -------------------- // + + /** + * storeActionLog + * Note: use $gBitSystem throughout that this function can be called statically if needed + * + * @param array $pParamHash + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + public static function storeActionLogFromHash( $pParamHash = NULL ) { + global $gBitSystem; + + if( $gBitSystem->isFeatureActive( 'liberty_action_log' ) && $this->verifyActionLog( $pParamHash ) ) { + $gBitSystem->mDb->associateInsert( BIT_DB_PREFIX."liberty_action_log", $pParamHash['action_log_store'] ); + } + } + + /** + * storeActionLog + * Note: use $gBitSystem throughout that this function can be called statically if needed + * + * @param array $pParamHash + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + public function storeActionLog( $pParamHash = NULL ) { + global $gBitSystem; + + if( !empty( $this ) && @BitBase::verifyId( $this->mContentId ) ) { + $pParamHash['action_log']['content_id'] = $this->mContentId; + } + if( !empty( $this->mInfo['title'] ) ) { + $pParamHash['action_log']['title'] = $this->mInfo['title']; + } + $log_message = ''; + if( empty( $pParamHash['action_log']['log_message'] ) && !empty( $this->mLogs ) ) { + foreach( $this->mLogs as $key => $msg ) { + $log_message .= "$msg"; + } + $pParamHash['action_log']['log_message'] = $log_message; + } + $error_message = ''; + if( empty( $pParamHash['action_log']['error_message'] ) && !empty( $this->mErrors ) ) { + foreach( $this->mErrors as $key => $msg ) { + $error_message .= "$msg\n"; + } + $pParamHash['action_log']['error_message'] = $error_message; + } + if( $gBitSystem->isFeatureActive( 'liberty_action_log' ) && static::verifyActionLog( $pParamHash ) ) { + $gBitSystem->mDb->associateInsert( BIT_DB_PREFIX."liberty_action_log", $pParamHash['action_log_store'] ); + } + } + + /** + * verify the data in the action log is ready for storing + * First checks $pParamHash['action_log'] for information and then the content_store stuff + * Note: use $gBitSystem throughout that this function can be called statically if needed + * + * @param array $pParamHash + * @return TRUE on success, FALSE on failure + */ + public static function verifyActionLog( &$pParamHash ) { + global $gBitUser, $gBitSystem; + + // we will set $ret FALSE if there is a problem along the way + // we can't populate mErrors since it would defeat the purpose having errors about the logging system + $ret = TRUE; + + // content_id isn't strictly needed + if( @BitBase::verifyId( $pParamHash['action_log']['content_id'] ) ) { + $pParamHash['action_log_store']['content_id'] = $pParamHash['action_log']['content_id']; + } elseif( @BitBase::verifyId( $pParamHash['content_id'] ) ) { + $pParamHash['action_log_store']['content_id'] = $pParamHash['content_id']; + } + // generic information needed in log + if( !empty( $pParamHash['action_log']['user_id'] ) ) { + $pParamHash['action_log_store']['user_id'] = $pParamHash['action_log']['user_id']; + } else { + $pParamHash['action_log_store']['user_id'] = $gBitUser->mUserId; + } + if( !empty( $pParamHash['action_log']['title'] ) ) { + $pParamHash['action_log_store']['title'] = $pParamHash['action_log']['title']; + } elseif( !empty( $pParamHash['content_store']['title'] ) ) { + $pParamHash['action_log_store']['title'] = $pParamHash['content_store']['title']; + } else { + $ret = FALSE; + } + // IP of the user + if( empty( $pParamHash['action_log']['ip'] ) ) { + if( !empty( $pParamHash['content_store']['ip'] ) ) { + $pParamHash['action_log']['ip'] = $pParamHash['content_store']['ip']; + } elseif( empty( $_SERVER["REMOTE_ADDR"] ) ) { + $pParamHash['action_log']['ip'] = '127.0.0.1'; + } else { + $pParamHash['action_log']['ip'] = $_SERVER["REMOTE_ADDR"]; + } + } + $pParamHash['action_log_store']['ip'] = $pParamHash['action_log']['ip']; + $pParamHash['action_log_store']['last_modified'] = $gBitSystem->getUTCTime(); + + // the log message + $log_message = ''; + if( empty( $pParamHash['action_log']['log_message'] ) && !empty( $this ) && !empty( $this->mLogs ) ) { + foreach( $this->mLogs as $key => $msg ) { + $log_message .= "$msg"; + } + } elseif( !empty( $pParamHash['action_log']['log_message'] ) ) { + $log_message = $pParamHash['action_log']['log_message']; + } + + // trim down log + if( !empty( $log_message ) ) { + $pParamHash['action_log_store']['log_message'] = substr( $log_message, 0, 250 ); + } + // error message - default is to put in any stuff in mErrors + $error_message = ''; + if( !empty( $pParamHash['action_log']['error_message'] ) ) { + $error_message = $pParamHash['action_log']['error_message']; + } + + // trim down error message + if( !empty( $error_message ) ) { + $pParamHash['action_log_store']['error_message'] = substr( $error_message, 0, 250 ); + } + + if( empty( $pParamHash['action_log_store']['error_message'] ) && empty( $pParamHash['action_log_store']['log_message'] )) { + $ret = FALSE; + } + // if we get as far as here, we can + return $ret; + } + + /** + * Get a list of action log entries + * + * @param array $pListHash List options + * @access public + * @return List of entries on success, FALSE on failure + */ + function getActionLogs( &$pListHash ) { + LibertyContent::prepGetList( $pListHash ); + + $ret = $bindVars = array(); + $selectSql = $joinSql = $orderSql = $whereSql = ''; + + if( !empty( $pListHash['find'] ) ) { + $whereSql .= empty( $whereSql ) ? ' WHERE ' : ' AND '; + $whereSql .= " UPPER( lal.`log_message` ) LIKE ? "; + $bindVars[] = '%'.strtoupper( $pListHash['find'] ).'%'; + } + + if( !empty( $pListHash['find_title'] ) ) { + $whereSql .= empty( $whereSql ) ? ' WHERE ' : ' AND '; + $whereSql .= " UPPER( lal.`title` ) LIKE ? "; + $bindVars[] = '%'.strtoupper( $pListHash['find_log'] ).'%'; + } + + if( !empty( $pListHash['user_id'] ) ) { + $whereSql .= empty( $whereSql ) ? ' WHERE ' : ' AND '; + $whereSql .= " lal.`user_id` = ? "; + $bindVars[] = $pListHash['user_id']; + } + + if( !empty( $pListHash['content_id'] ) ) { + $whereSql .= empty( $whereSql ) ? ' WHERE ' : ' AND '; + $whereSql .= " lal.`content_id` = ? "; + $bindVars[] = $pListHash['content_id']; + } + + if( !empty( $pListHash['sort_mode'] )) { + if( preg_match( "/^last_modified|^title/", $pListHash['sort_mode'] )) { + $pListHash['sort_mode'] = "lal.".$pListHash['sort_mode']; + } + $orderSql = " ORDER BY ".$this->convertSortMode( $pListHash['sort_mode'] )." "; + } + + $query = " + SELECT lal.*, + lc.`content_type_guid`, lc.`created`, lct.`content_name`, lct.`content_name_plural`, + uue.`login` AS modifier_user, uue.`real_name` AS modifier_real_name + FROM `".BIT_DB_PREFIX."liberty_action_log` lal + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON ( lc.`content_id` = lal.`content_id` ) + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_types` lct ON ( lct.`content_type_guid` = lc.`content_type_guid` ) + LEFT OUTER JOIN `".BIT_DB_PREFIX."users_users` uue ON ( uue.`user_id` = lal.`user_id` ) + $whereSql $orderSql"; + + $result = $this->mDb->query( $query, $bindVars, $pListHash['max_records'], $pListHash['offset'] ); + + while( $aux = $result->fetchRow() ) { + $aux['user'] = $aux['modifier_user']; + $aux['editor'] = ( isset( $aux['modifier_real_name'] ) ? $aux['modifier_real_name'] : $aux['modifier_user'] ); + $aux['display_name'] = BitUser::getDisplayNameFromHash( $aux ); + $ret[] = $aux; + } + + $query = "SELECT COUNT( lal.`user_id` ) FROM `".BIT_DB_PREFIX."liberty_action_log` lal $whereSql"; + $pListHash['cant'] = $this->mDb->getOne( $query, $bindVars ); + LibertyContent::postGetList( $pListHash ); + + return $ret; + } + + /** + * expungeActionLog + * + * @param array $pTimeSpan Anything older than this timespan will be removed + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function expungeActionLog( $pTimeSpan = NULL ) { + global $gBitSystem; + $where = ''; + $bindVars = array(); + if( @BitBase::verifyId( $pTimeSpan ) ) { + $where = "WHERE `last_modified` < ?"; + $bindVars[] = $gBitSystem->mServerTimestamp->getUTCTime() - $pTimeSpan; + } + $this->mDb->query( "DELETE FROM `".BIT_DB_PREFIX."liberty_action_log` $where", $bindVars ); + return TRUE; + } + + /** + * getAvailableContentStatus + * + * @access public + * @return an array of content_status_id, content_status_names the current + * user can use on this content. Subclases may easily override with return + * LibertyContent::getAvailableContentStatus(-100, 0) for example to restrict to + * only hidden content types. + */ + function getAvailableContentStatuses( $pUserMinimum=-100, $pUserMaximum=100 ) { + global $gBitUser; + if( $gBitUser->hasPermission( 'p_liberty_edit_all_status' )) { + return( $this->mDb->getAssoc( "SELECT `content_status_id`,`content_status_name` FROM `".BIT_DB_PREFIX."liberty_content_status` ORDER BY `content_status_id`" ) ); + } else { + return( $this->mDb->getAssoc( "SELECT `content_status_id`, `content_status_name` FROM `".BIT_DB_PREFIX."liberty_content_status` WHERE `content_status_id` > ? AND `content_status_id` < ? ORDER BY `content_status_id`", array( $pUserMinimum, $pUserMaximum ))); + } + } + + /** + * getContentStatus will return the content status of the currently loaded content. + * + * @param array $pContentId Content ID of the content in question + * @access public + * @return Status ID + */ + function getContentStatus( $pDefault = 50, $pContentId = NULL ) { + $ret = NULL; + if ( @!BitBase::verifyId( $pContentId ) && $this->isValid() ){ + if ( !( $ret = $this->getField( 'content_status_id' ) ) ){ + $pContentId = $this->mContentId; + } + } + if( !is_null( $pContentId )) { + $ret = $this->mDb->getOne( "SELECT `content_status_id` FROM `".BIT_DB_PREFIX."liberty_content` WHERE `content_id` = ?", array( $pContentId )); + } + $ret = is_null( $ret ) ? $pDefault : $ret; + return $ret; + } + + /** + * isDeleted status test + * + * @return true when the content status = -999 + */ + function isDeleted() { + global $gBitSystem; + return( $this->getField( 'content_status_id' ) <= $gBitSystem->getConfig( 'liberty_status_deleted', -999 ) ); + } + + /** + * isPrivate status test + * + * @return true when the content status = -999 + */ + function isPrivate() { + global $gBitSystem; + return( $this->getField( 'content_status_id' ) <= $gBitSystem->getConfig( 'liberty_status_threshold_private', -40 ) ); + } + + /** + * isProtected status test + * + * @return true when the content status = -20 or content has protection flag set + */ + function isProtected() { + global $gBitSystem; + return( $this->getField( 'content_status_id' ) <= $gBitSystem->getConfig( 'liberty_status_threshold_protected', -20 ) ); + } + + /** + * isHidden status test + * + * @return true when the content status = -10 + */ + function isHidden() { + global $gBitSystem; + return( $this->getField( 'content_status_id' ) <= $gBitSystem->getConfig( 'liberty_status_threshold_hidden', -10 ) ); + } + + /** + * isHidden status test + * + * @return true when the content status = -10 + */ + function isPublic() { + global $gBitSystem; + return( $this->getField( 'content_status_id' ) >= $gBitSystem->getConfig( 'liberty_status_threshold_public', 50 ) ); + } + + /** + * getContentStatusName + * + * @param array $pStatusId Status ID if not available in $this->mInfo['content_status_id'] + * @access public + * @return The name of the content status based on the status id of the content + */ + function getContentStatusName( $pStatusId = NULL ) { + $ret = 'Not a valid content status'; + + // check to see where we can get the status information from + if( !empty( $this ) && !empty( $this->mInfo['content_status_name'] )) { + return( $this->mInfo['content_status_name'] ); + } elseif( is_null( $pStatusId ) && !empty( $this ) && !empty( $this->mInfo['content_status_id'] )) { + $pStatusId = $this->mInfo['content_status_id']; + } + + // fetch from db if needed + if( !is_null( $pStatusId )) { + if( $ret = $this->mDb->getOne( "SELECT `content_status_name` FROM `".BIT_DB_PREFIX."liberty_content_status` WHERE `content_status_id` = ?", array( $pStatusId ))) { + } + } + + return $ret; + } + + /** + * Store Data into liberty_content_data + * + * @return bool true ( will not currently report a failure ) + */ + function storeData( $pData, $pType ) { + if( $this->mContentId ) { + $pData = trim( $pData ); + if( empty( $pData ) ) { + $this->mDb->query( "DELETE FROM `".BIT_DB_PREFIX."liberty_content_data` WHERE `content_id`=? AND `data_type`=?", array( $this->mContentId, $pType ) ); + } else { + if( $this->mDb->getOne( "SELECT `content_id` FROM `".BIT_DB_PREFIX."liberty_content_data` WHERE `content_id`=? AND `data_type`=?", array( $this->mContentId, $pType ) ) ) { + $query = "UPDATE `".BIT_DB_PREFIX."liberty_content_data` SET `data`= ? WHERE `content_id` = ? AND `data_type`=?"; + } else { + $query = "INSERT INTO `".BIT_DB_PREFIX."liberty_content_data` ( `data`, `content_id`, `data_type` ) VALUES (?,?,?)"; + } + $result = $this->mDb->query( $query, array( $pData, $this->mContentId, $pType ) ); + } + } + return TRUE; + } + + /** + * storeStatus store liberty contenet status + * + * @param array $pContentStatusId + * @access public + * @return void + */ + function storeStatus( $pContentStatusId ) { + if( $this->isValid() && $pContentStatusId ) { + return $this->mDb->query( "UPDATE `".BIT_DB_PREFIX."liberty_content` SET `content_status_id`=? WHERE `content_id`=?", array( $pContentStatusId, $this->mContentId ) ); + } + } + + /** + * isCommentable will check allow_comments in mInfo or if it's set as a preference. + * + * @access public + * @return TRUE on success, FALSE on failure + */ + function isCommentable() { + if( $this->getPreference( 'allow_comments' ) == 'y' ) { + return TRUE; + } else { + $setting = $this->getField( 'allow_comments' ); + return( $setting == TRUE || $setting == 'y' ); + } + } + + /** + * getListingPreview -- Returns a string with a preview of the content. + * @access public + * @return the preview string + **/ + function getListingPreview( $pMixed ) { + global $gBitSystem, $gContent, $gBitSmarty; + // TODO! + return $ret; + } + + /** + * getPreview -- Returns a string with a preview of the content. Default implementation runs getRenderFile() with $liberty_preview set in the context and gBitSystem set to only render the content. + * + * @access public + * @return the preview string + **/ + function getPreview() { + global $gBitSystem, $gContent, $gBitSmarty, $gBitThemes; + // Tell gBitSystem not to do modules and such + $gBitThemes->setFormatHeader( "center_only" ); + // Tell the content we are previewing (in case they care) + $gBitSmarty->assign('liberty_preview', true); + // Save current gContent + $oldGContent = $gContent; + // Make us the content + $gContent = $this; + + $ret = get_include_contents($this->getRenderFile()); + + // Return gBitSystem to full render mode + $gBitThemes->setFormatHeader( "html" ); + // Clear the preview flag + $gBitSmarty->assign('liberty_preview', false); + // Restore gContent + $gContent = $oldGContent; + + return $ret; + } + +} diff --git a/includes/classes/LibertyMime.php b/includes/classes/LibertyMime.php new file mode 100644 index 0000000..041f0d0 --- /dev/null +++ b/includes/classes/LibertyMime.php @@ -0,0 +1,1267 @@ +<?php +/** + * Manages liberty Uploads + * + * @package liberty + */ + +/** + * required setup + */ +require_once( LIBERTY_PKG_CLASS_PATH.'LibertyContent.php' ); + +// load the image processor plugin, check for loaded 'gd' since that is the default processor, and config might not be set. +if( $gBitSystem->isFeatureActive( 'image_processor' ) || extension_loaded( 'gd' ) ) { + require_once( LIBERTY_PKG_PATH."plugins/processor.".$gBitSystem->getConfig( 'image_processor','gd' ).".php" ); +} + +// maximum size of the 'original' image when converted to jpg +define( 'MAX_THUMBNAIL_DIMENSION', 20000 ); + +/** + * LibertyMime class + * + * @package liberty + */ +class LibertyMime extends LibertyContent { + public $mStoragePrefs = NULL; + + /** + * load the attachments for a given content id and then stuff them in mStorage + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + public function load() { + global $gLibertySystem; + if( @BitBase::verifyId( $this->mContentId )) { + // load up the content + LibertyContent::load(); + + // don't loadAttachmentPreferences() when we are forcing the installer since it breaks the login process before 2.1.0-beta + if( !defined( 'INSTALLER_FORCE' ) && !defined( 'LOGIN_VALIDATE' )) { + $this->loadAttachmentPreferences(); + } + + $query = "SELECT * FROM `".BIT_DB_PREFIX."liberty_attachments` la WHERE la.`content_id`=? ORDER BY la.`pos` ASC, la.`attachment_id` ASC"; + if( $result = $this->mDb->query( $query,array( $this->mContentId ))) { + $this->mStorage = array(); + while( $row = $result->fetchRow() ) { + if( !empty( $row['is_primary'] ) ) { + // used by edit tpl's among other things + $this->mInfo['primary_attachment_id'] = $row['attachment_id']; + } elseif( !$this->getField( 'primary_attachment_id' ) && !empty( $row['attachment_id'] ) ) { + // primary was not set by the above, default to first row. might be reset by later iterations via if is_primary above + $this->mInfo['primary_attachment_id'] = $row['attachment_id']; + } + if( $func = $gLibertySystem->getPluginFunction( $row['attachment_plugin_guid'], 'load_function', 'mime' )) { + // we will pass the preferences by reference that the plugin can easily update them + if( empty( $this->mStoragePrefs[$row['attachment_id']] )) { + $this->mStoragePrefs[$row['attachment_id']] = array(); + } + $this->mStorage[$row['attachment_id']] = $func( $row, $this->mStoragePrefs[$row['attachment_id']], NULL ); + } else { + print "No load_function for ".$row['attachment_plugin_guid']; + } + } + } + } + return( TRUE ); + } + + /** + * Store a new upload + * + * @param array $pStoreHash contains all data to store the gallery + * @return bool TRUE on success, FALSE if store could not occur. If FALSE, $this->mErrors will have reason why + * @access public + **/ + public function store( &$pStoreHash ) { + global $gLibertySystem; + // make sure all the data is in order + if( LibertyMime::verify( $pStoreHash ) && ( !empty( $pStoreHash['skip_content_store'] ) || parent::store( $pStoreHash ) ) ) { + $this->StartTrans(); + // files have been uploaded + if( !empty( $pStoreHash['upload_store']['files'] ) && is_array( $pStoreHash['upload_store']['files'] )) { + + foreach( $pStoreHash['upload_store']['files'] as $key => $upload ) { + // if we don't have an upload, we'll simply update the file settings using the mime plugins + if( empty( $upload['tmp_name'] )) { + if( @BitBase::verifyId( $upload['attachment_id'] )) { + // since the form might have all options unchecked, we need to call the update function regardless + // currently i can't think of a better way to get the plugin guid back when $pStoreHash[plugin] is + // empty. - xing - Friday Jul 11, 2008 20:21:18 CEST + if( !empty( $this->mStorage[$upload['attachment_id']] )) { + $attachment = $this->mStorage[$upload['attachment_id']]; + $data = array(); + if( !empty( $pStoreHash['plugin'][$upload['attachment_id']][$attachment['attachment_plugin_guid']] )) { + $data = $pStoreHash['plugin'][$upload['attachment_id']][$attachment['attachment_plugin_guid']]; + } + if( !$this->updateAttachmentParams( $upload['attachment_id'], $attachment['attachment_plugin_guid'], $data )) { + $this->mErrors['attachment_update'] = "There was a problem updating the file settings."; + } + } + } + // skip rest of process + continue; + } + + $storeRow = $pStoreHash['upload_store']; + unset( $storeRow['files'] ); + + // copy by reference that filetype changes are made in lookupMimeHandler() + $storeRow['upload'] = &$upload; + if( isset( $pStoreHash['thumbnail'] ) ) { + $storeRow['upload']['thumbnail'] = $pStoreHash['thumbnail']; + } + + // when content is created the content_id is only available after LibertyContent::store() + $storeRow['content_id'] = $pStoreHash['content_id']; + + // let the plugin do the rest + $guid = $gLibertySystem->lookupMimeHandler( $upload ); + $this->pluginStore( $storeRow, $guid, @BitBase::verifyId( $upload['attachment_id'] )); + + // finally, we need to update the original hash with the new values + $pStoreHash['upload_store']['files'][$key] = $storeRow; + } + } + + // some mime plugins might not have file uploads - these plugins will tell us what mime handlers they are using + if( !empty( $pStoreHash['mimeplugin'] ) && is_array( $pStoreHash['mimeplugin'] )) { + foreach( $pStoreHash['mimeplugin'] as $guid => $storeRow ) { + // check to see if we have anything worth storing in the array + $plugin_store = FALSE; + foreach( array_values( $storeRow ) as $value ) { + if( !empty( $value )) { + $plugin_store = TRUE; + } + } + + if( !empty( $plugin_store )) { + // when content is created the content_id is only available after LibertyContent::store() + $storeRow['content_id'] = $pStoreHash['content_id']; + $this->pluginStore( $storeRow, $guid, @BitBase::verifyId( $upload['attachment_id'] )); + } + } + } + + // deal with the primary attachment after we've dealt with all the files + $this->setPrimaryAttachment( + $pStoreHash['liberty_attachments']['primary'], + $pStoreHash['content_id'], + empty( $pStoreHash['liberty_attachments']['auto_primary'] ) || $pStoreHash['liberty_attachments']['auto_primary'] ? TRUE : FALSE + ); + + // Roll back if something went wrong + if( empty( $this->mErrors )) { + $this->CompleteTrans(); + } else { + $this->mDb->RollbackTrans(); + } + } + + return( count( $this->mErrors ) == 0 ); + } + + /** + * pluginStore will use a given plugin to store uploaded file data + * + * @param string $pGuid GUID of plugin + * @param array $pStoreHash Data to be prcessed and stored by the plugin + * @param boolean $pUpdate set to TRUE if this is just an update + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + public function pluginStore( &$pStoreHash, $pGuid, $pUpdate = FALSE ) { + global $gLibertySystem; + if( !empty( $pStoreHash ) && $verify_function = $gLibertySystem->getPluginFunction( $pGuid, 'verify_function' )) { + // pass along a pointer to the content object + $pStoreHash['this'] = &$this; + // verify the uploaded file using the plugin + if( $verify_function( $pStoreHash )) { + if( $process_function = $gLibertySystem->getPluginFunction( $pGuid, (( $pUpdate ) ? 'update_function' : 'store_function' ))) { + if( !$process_function( $pStoreHash )) { + $this->mErrors = array_merge( $this->mErrors, $pStoreHash['errors'] ); + } + } else { + $this->mErrors['store_function'] = tra( 'No suitable store function found.' ); + } + } else { + $this->mErrors = array_merge( $this->mErrors, $pStoreHash['errors'] ); + } + } else { + $this->mErrors['verify_function'] = tra( 'No suitable verify function found.' ); + } + + return( count( $this->mErrors ) == 0 ); + } + + /** + * Verify content that is about to be stored + * + * @param array $pStoreHash hash of all data that needs to be stored in the database + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason + * @todo If one of the uploaded files is an update, place the attachment_id with the upload hash in $_FILES or in _files_override + */ + public function verify( &$pParamHash ) { + global $gBitUser, $gLibertySystem; + + // check to see if we have any files to upload + if( isset( $pParamHash['_files_override'] )) { + // we have been passed in a manually stuffed files attachment, such as a custom uploader would have done. + // process this, and skip over $_FILES + $uploads = $pParamHash['_files_override']; + } elseif( !empty( $_FILES )) { + // we have some _FILES hanging around we will gobble up. This is inherently dagnerous chewing up a _FILES like this as + // it can cause premature storing of a _FILE if you are trying to store multiple pieces of content at once. + foreach( $_FILES as $key => $file ) { + if( !empty( $file['name'] ) || !empty( $file['attachment_id'] )) { + $uploads[$key] = $file; + } + } + } + + // verify uploads + if( !empty( $uploads ) ) { + foreach( array_keys( $uploads ) as $file ) { + $pParamHash['upload_store']['files'][$file] = LibertyMime::verifyAttachment( $uploads[$file] ); + } + } + + // don't check for p_liberty_attach_attachments permission on bitpermuser class so registration with avatar upload works + if( strtolower( get_class( $this )) == 'bitpermuser' ) { + $pParamHash['upload_store']['no_perm_check'] = TRUE; + } + + // check for the required permissions to upload a file to the liberty attachments area + if( !empty( $uploads ) && empty( $pParamHash['no_perm_check'] )) { + if( !$this->hasUserPermission( 'p_liberty_attach_attachments' )) { + $this->mErrors['permission'] = tra( 'You do not have permission to upload attachments.' ); + } + } + + // primary attachment. Allow 'none' to clear the primary. + if( !@BitBase::verifyId( $pParamHash['liberty_attachments']['primary'] ) && ( empty( $pParamHash['liberty_attachments']['primary'] ) || $pParamHash['liberty_attachments']['primary'] != 'none' ) ) { + $pParamHash['liberty_attachments']['primary'] = NULL; + } + + // if we have an error we get them all by checking parent classes for additional errors + if( count( $this->mErrors ) > 0 ){ + // check errors of LibertyContent since LibertyMime means to override the parent verify + LibertyContent::verify( $pParamHash ); + } + + return ( count( $this->mErrors ) == 0 ); + } + + /** + * getThumbnailUrl will fetch the primary thumbnail for a given content. If nothing has been set, it will fetch the last thumbnail it can find. + * + * @param string $pSize + * @param array $pInfoHash + * @access public + * @return boolean TRUE on success, FALSE on failure - $this->mErrors will contain reason for failure + */ + public function getThumbnailUrl( $pSize='small', $pInfoHash=NULL, $pSecondary=NULL, $pDefault=TRUE ) { + $ret = NULL; + if( !empty( $pInfoHash ) ) { + // do some stuff if we are given a hash of stuff + } elseif( $this->isValid() && !empty( $this->mStorage ) ) { + foreach( array_keys( $this->mStorage ) as $attachmentId ) { + if( !empty( $this->mStorage[$attachmentId]['is_primary'] ) ) { + break; + } + } + if( !empty( $this->mStorage[$attachmentId]['thumbnail_url'][$pSize] )) { + $ret = $this->mStorage[$attachmentId]['thumbnail_url'][$pSize]; + } + } + if( $pDefault && empty( $ret ) ) { + $ret = parent::getThumbnailUrl( $pSize, $pInfoHash, $pSecondary ); + } + return $ret; + } + + /** + * updateAttachmentParams will update attachment parameters + * + * @param numeric $pAttachmentId attachment_id of the item we want the prefs from (optional) + * @param string $pPluginGuid GUID of the plugin that should process the data + * @param array $pParamHash Data to be processed by the plugin + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + public function updateAttachmentParams( $pAttachmentId, $pPluginGuid, $pParamHash = array() ) { + global $gLibertySystem; + $ret = FALSE; + + if( BitBase::verifyId( $pAttachmentId )) { + if( !empty( $this ) && !empty( $this->mStorage[$pAttachmentId] )) { + $file = $this->mStorage[$pAttachmentId]; + } else { + $file = $this->getAttachment( $pAttachmentId ); + } + + if( @BitBase::verifyId( $file['attachment_id'] ) && !empty( $pPluginGuid ) && ( $update_function = $gLibertySystem->getPluginFunction( $pPluginGuid, 'update_function', 'mime' ))) { + if( $update_function( $file, $pParamHash )) { + $ret = TRUE; + } else { + if( !empty( $file['errors'] )) { + $this->mErrors['param_update'] = $file['errors']; + } else { + $this->mErrors['param_update'] = tra( 'There was an unspecified error while updating the file.' ); + } + } + } + } + return $ret; + } + + /** + * verifyAttachment will perform a generic check if a file is valid for processing + * + * @param array $pFile file array from $_FILES + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + public function verifyAttachment( $pFile ) { + if( !empty( $pFile['tmp_name'] ) && is_file( $pFile['tmp_name'] ) && empty( $pFile['error'] ) || !empty( $pFile['attachment_id'] )) { + return $pFile; + } + } + + /** + * Increment the item hit flag by 1 + * + * @access public + * @param numeric $pAttachmentId Attachment ID + * @return adodb query result or FALSE + * @note we're abusing the hits column for download count. + */ + public static function addDownloadHit( $pAttachmentId = NULL ) { + global $gBitUser, $gBitSystem; + if( @BitBase::verifyId( $pAttachmentId ) && $attachment = static::loadAttachment( $pAttachmentId )) { + if( !$gBitUser->isRegistered() || ( $gBitUser->isRegistered() && $gBitUser->mUserId != $attachment['user_id'] )) { + $bindVars = array( $pAttachmentId ); + if( $gBitSystem->mDb->getOne( "SELECT `attachment_id` FROM `".BIT_DB_PREFIX."liberty_attachments` WHERE `attachment_id` = ? AND `hits` IS NULL", $bindVars )) { + $query = "UPDATE `".BIT_DB_PREFIX."liberty_attachments` SET `hits` = 1 WHERE `attachment_id` = ?"; + } else { + $query = "UPDATE `".BIT_DB_PREFIX."liberty_attachments` SET `hits` = `hits`+1 WHERE `attachment_id` = ?"; + } + return $gBitSystem->mDb->query( $query, $bindVars ); + } + } + return FALSE; + } + + // {{{ =================== Storage Directory Methods ==================== + function getSourceUrl( $pParamHash=array() ) { + $ret = NULL; + if( empty( $pParamHash ) && !empty( $this ) ) { + $pParamHash = $this->mInfo; + } + if( $fileName = $this->getParameter( $pParamHash, 'file_name', $this->getField( 'file_name' ) ) ) { + $defaultFileName = liberty_mime_get_default_file_name( $fileName, $pParamHash['mime_type'] ); + if( file_exists( $this->getStoragePath( $pParamHash ).$defaultFileName ) ) { + $ret = $this->getStorageUrl( $pParamHash ).$defaultFileName; + } else { + $ret = $this->getStorageUrl( $pParamHash ).basename( $fileName ); + } + } + return $ret; + } + + function getSourceFile( $pParamHash=array() ) { + $ret = NULL; + if( empty( $pParamHash ) && !empty( $this ) ) { + $pParamHash = $this->mInfo; + } + if( $fileName = $this->getParameter( $pParamHash, 'file_name', $this->getField( 'file_name' ) ) ) { + $defaultFileName = liberty_mime_get_default_file_name( $fileName, $pParamHash['mime_type'] ); + $ret = $this->getStoragePath( $pParamHash ).$defaultFileName; + if( !file_exists( $ret ) ) { + $ret = $this->getStoragePath( $pParamHash ).basename( $fileName ); + } + } + return $ret; + } + + + /** + * getStoragePath - get path to store files for the feature site_upload_dir. It creates a calculable hierarchy of directories + * + * @access public + * @author Christian Fowler<spider@steelsun.com> + * @param $pSubDir any desired directory below the StoragePath. this will be created if it doesn't exist + * @param $pCommon indicates not to use the 'common' branch, and not the 'users/.../<user_id>' branch + * @param $pRootDir override BIT_ROOT_DIR with a custom absolute path - useful for areas where no we access should be allowed + * @return string full path on local filsystem to store files. + */ + function getStoragePath( $pParamHash, $pRootDir=NULL ) { + $ret = null; + + if( $branch = liberty_mime_get_storage_branch( $pParamHash ) ) { + $ret = ( !empty( $pRootDir ) ? $pRootDir : STORAGE_PKG_PATH ).$branch; + mkdir_p($ret); + } + return $ret; + } + + + function getStorageUrl( $pParamHash ) { + return STORAGE_PKG_URL.liberty_mime_get_storage_branch( $pParamHash ); + } + + /** + * getStorageBranch - get url to store files for the feature site_upload_dir. It creates a calculable hierarchy of directories + * + * @access public + * @author Christian Fowler<spider@steelsun.com> + * @param $pSubDir any desired directory below the StoragePath. this will be created if it doesn't exist + * @param $pUserId indicates the 'users/.../<user_id>' branch or use the 'common' branch if null + * @param $pRootDir **deprecated, unused, will be removed in future relase**. + * @return string full path on local filsystem to store files. + */ + function getStorageBranch( $pParamHash ) { + return liberty_mime_get_storage_branch( $pParamHash ); + } + + /** + * getStorageSubDirName get a filename based on the uploaded file + * + * @param array $pFileHash File information provided in $_FILES + * @access public + * @return appropriate sub dir name + */ + function getStorageSubDirName( $pFileHash = NULL ) { + if( !empty( $pFileHash['mime_type'] ) && strstr( $pFileHash['mime_type'], "/" )) { + $ret = strtolower( preg_replace( "!/.*$!", "", $pFileHash['mime_type'] )); + // if we only got 'application' we will use the file extenstion + if( $ret == 'application' && !empty( $pFileHash['name'] ) && ( $pos = strrpos( $pFileHash['name'], "." )) !== FALSE ) { + $ret = strtolower( substr( $pFileHash['name'], $pos + 1 )); + } + } + + // append an 's' to not create an image and images dir side by side (legacy reasons) + if( empty( $ret ) || $ret == 'image' ) { + $ret = 'images'; + } + + return $ret; + } + + /** + * validateStoragePath make sure that the file/dir you are trying to delete is valid + * + * @param array $pPath absolute path to the file/dir we want to validate + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + public static function validateStoragePath( $pPath ) { + // file_exists checks for file or directory + if( !empty( $pPath ) && $pPath = realpath( $pPath )) { + // ensure path sanity + if( preg_match( "#^".realpath( STORAGE_PKG_PATH )."/(users|common)/\d+/\d+/\w+/\d+#", $pPath )) { + return $pPath; + } + } + } + + // }}} + + + // {{{ =================== Attachment Methods ==================== + /** + * Get a list of all available attachments + * + * @param array $pListHash + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getAttachmentList( &$pListHash ) { + global $gLibertySystem, $gBitUser, $gBitSystem; + + LibertyContent::prepGetList( $pListHash ); + + // initialise some variables + $attachments = $ret = $bindVars = array(); + $whereSql = $joinSql = $selectSql = ''; + + // only admin may view attachments from other users + if( !$gBitUser->isAdmin() ) { + $pListHash['user_id'] = $gBitUser->mUserId; + } + + if( !empty( $pListHash['user_id'] ) ) { + $whereSql .= empty( $whereSql ) ? ' WHERE ' : ' AND '; + $whereSql .= " la.user_id = ? "; + $bindVars[] = $pListHash['user_id']; + } + + if( !empty( $pListHash['content_id'] ) ) { + $whereSql .= empty( $whereSql ) ? ' WHERE ' : ' AND '; + $whereSql .= " la.`content_id` = ? "; + $selectSql .= " , la.`content_id` "; + $bindVars[] = $pListHash['content_id']; + } + $query = "SELECT la.* $selectSql FROM `".BIT_DB_PREFIX."liberty_attachments` la INNER JOIN `".BIT_DB_PREFIX."users_users` uu ON(la.`user_id` = uu.`user_id`) $joinSql $whereSql"; + $result = $this->mDb->query( $query, $bindVars, $pListHash['max_records'], $pListHash['offset'] ); + while( $res = $result->fetchRow() ) { + $attachments[] = $res; + } + + foreach( $attachments as $attachment ) { + if( $loadFunc = $gLibertySystem->getPluginFunction( $attachment['attachment_plugin_guid'], 'load_function', 'mime' )) { + /* @$prefs - quick hack to stop LibertyMime plugins from breaking until migration to LibertyMime is complete + * see expected arguments of liberty/plugins/mime.default.php::mime_default_load -wjames5 + */ + $prefs = array(); + $ret[$attachment['attachment_id']] = $loadFunc( $attachment, $prefs ); + } + } + + // count all entries + $query = "SELECT COUNT(*) + FROM `".BIT_DB_PREFIX."liberty_attachments` la + INNER JOIN `".BIT_DB_PREFIX."users_users` uu ON(la.`user_id` = uu.`user_id`) + $joinSql $whereSql + "; + + $pListHash['cant'] = $this->mDb->getOne( $query, $bindVars ); + $this->postGetList( $pListHash ); + + return $ret; + } + + /** + * Expunges the content deleting attached attachments + */ + function expunge() { + if( !empty( $this->mStorage ) && count( $this->mStorage )) { + foreach( array_keys( $this->mStorage ) as $i ) { + $this->expungeAttachment( $this->mStorage[$i]['attachment_id'] ); + } + } + return LibertyContent::expunge(); + } + + /** + * expunge attachment from the database (and file system via the plugin if required) + * + * @param numeric $pAttachmentId attachment id of the item that should be deleted + * @access public + * @return TRUE on success, FALSE on failure + */ + function expungeAttachment( $pAttachmentId ) { + global $gLibertySystem, $gBitUser; + $ret = NULL; + if( @$this->verifyId( $pAttachmentId ) ) { + $sql = "SELECT `attachment_plugin_guid`, `user_id` FROM `".BIT_DB_PREFIX."liberty_attachments` WHERE `attachment_id` = ?"; + if(( $row = $this->mDb->getRow( $sql, array( $pAttachmentId ))) && ( $this->isOwner( $row ) || $gBitUser->isAdmin() )) { + // check if we have the means available to remove this attachment + if(( $guid = $row['attachment_plugin_guid'] ) && $expungeFunc = $gLibertySystem->getPluginFunction( $guid, 'expunge_function', 'mime' )) { + // --- Do the final cleanup of liberty related tables --- + + // there might be situations where we remove user images including portrait, avatar or logo + // This needs to happen before the plugin can do it's work due to constraints + $types = array( 'portrait', 'avatar', 'logo' ); + foreach( $types as $type ) { + $sql = "UPDATE `".BIT_DB_PREFIX."users_users` SET `{$type}_attachment_id` = NULL WHERE `{$type}_attachment_id` = ?"; + $this->mDb->query( $sql, array( $pAttachmentId )); + } + + if( $expungeFunc( $pAttachmentId )) { + // Delete the attachment meta data, prefs and record. + $sql = "DELETE FROM `".BIT_DB_PREFIX."liberty_attachment_meta_data` WHERE `attachment_id` = ?"; + $this->mDb->query( $sql, array( $pAttachmentId )); + $sql = "DELETE FROM `".BIT_DB_PREFIX."liberty_attachment_prefs` WHERE `attachment_id` = ?"; + $this->mDb->query( $sql, array( $pAttachmentId )); + $sql = "DELETE FROM `".BIT_DB_PREFIX."liberty_attachments` WHERE `attachment_id`=?"; + $this->mDb->query( $sql, array( $pAttachmentId )); + + // Remove attachment from memory + unset( $this->mStorage[$pAttachmentId] ); + $ret = TRUE; + } + } else { + print( "Expunge function not found for this content!" ); + } + } + } + + return $ret; + } + + /** + * loadAttachment will load details of a given attachment + * + * @param numeric $pAttachmentId Attachment ID of the attachment + * @param array $pParams optional parameters that might contain information like display thumbnail size + * @access public + * @return attachment details + */ + public static function loadAttachment( $pAttachmentId, $pParams = NULL ) { + global $gLibertySystem, $gBitSystem; + $ret = NULL; + + if( @BitBase::verifyId( $pAttachmentId )) { + $query = "SELECT * FROM `".BIT_DB_PREFIX."liberty_attachments` la WHERE la.`attachment_id`=?"; + if( $result = $gBitSystem->mDb->query( $query, array( (int)$pAttachmentId ))) { + if( $row = $result->fetchRow() ) { + if( $func = $gLibertySystem->getPluginFunction( $row['attachment_plugin_guid'], 'load_function', 'mime' )) { + $sql = "SELECT `pref_name`, `pref_value` FROM `".BIT_DB_PREFIX."liberty_attachment_prefs` WHERE `attachment_id` = ?"; + $prefs = $gBitSystem->mDb->getAssoc( $sql, array( $pAttachmentId )); + $ret = $func( $row, $prefs, $pParams ); + } + } + } + } + return $ret; + } + + /** + * getAttachment will load details of a given attachment + * + * @param numeric $pAttachmentId Attachment ID of the attachment + * @param array $pParams optional parameters that might contain information like display thumbnail size + * @access public + * @return attachment details + */ + public function getAttachment( $pAttachmentId, $pParams = NULL ) { + global $gLibertySystem, $gBitSystem; + $ret = NULL; + + if( @BitBase::verifyId( $pAttachmentId )) { + $query = "SELECT * FROM `".BIT_DB_PREFIX."liberty_attachments` la WHERE la.`attachment_id`=?"; + if( $result = $gBitSystem->mDb->query( $query, array( (int)$pAttachmentId ))) { + if( $row = $result->fetchRow() ) { + if( $func = $gLibertySystem->getPluginFunction( $row['attachment_plugin_guid'], 'load_function', 'mime' )) { + $prefs = array(); + // if the object is available, we'll copy the preferences by reference to allow the plugin to update them as needed + if( !empty( $this ) && !empty( $this->mStoragePrefs[$pAttachmentId] )) { + $prefs = &$this->mStoragePrefs[$pAttachmentId]; + } else { + $prefs = static::getAttachmentPreferences( $pAttachmentId ); + } + $ret = $func( $row, $prefs, $pParams ); + } + } + } + } + return $ret; + } + + /** + * setPrimaryAttachment will set is_primary 'y' for the specified + * attachment and will ensure that all others are set to 'n' + * + * @param mixed $pAttachmentId attachment id of the item we want to + * set as the primary attachment. Use 'none' to clear. + * @param numeric $pContentId content id we are working with. + * @param boolean $pAutoPrimary automatically set primary if there is only + * one attachment. Defaults to true. + * @access public + * @return TRUE on success, FALSE on failure + */ + public function setPrimaryAttachment( $pAttachmentId = NULL, $pContentId = NULL, $pAutoPrimary = TRUE ) { + global $gBitSystem; + + $ret = FALSE; + + // If we are not given an attachment id but we where told the + // content_id and we are supposed to auto set the primary then + // figure out which one it is + if( !@BitBase::verifyId( $pAttachmentId ) && ( empty( $pAttachmentId ) || $pAttachmentId != 'none' ) && @BitBase::verifyId( $pContentId ) && $pAutoPrimary ) { + $query = " + SELECT `attachment_id` + FROM `".BIT_DB_PREFIX."liberty_content` lc + INNER JOIN `".BIT_DB_PREFIX."liberty_attachments` la ON( lc.`content_id` = la.`content_id` ) + WHERE lc.`content_id` = ?"; + $pAttachmentId = $this->mDb->getOne( $query, array( $pContentId )); + } + + // If we have an attachment_id we'll set it to this + if( @BitBase::verifyId( $pAttachmentId )) { + // get attachment we want to set primary + $attachment = $this->getAttachment( $pAttachmentId ); + + // Clear old primary. There can only be one! + $this->clearPrimaryAttachment( $attachment['content_id'] ); + + // now update the attachment to is_primary + $query = " + UPDATE `".BIT_DB_PREFIX."liberty_attachments` + SET `is_primary` = ? WHERE `attachment_id` = ?"; + $this->mDb->query( $query, array( 'y', $pAttachmentId )); + + $ret = TRUE; + // Otherwise, are we supposed to clear the primary entirely? + } elseif( @BitBase::verifyId( $pContentId ) && !empty( $pAttachmentId ) && $pAttachmentId == 'none' ) { + // Okay then do the job + $this->clearPrimaryAttachment( $pContentId ); + } + + return $ret; + } + + /** + * clearPrimaryAttachment will remove the primary flag for all attachments + * with the given content_id + * + * @param numeric the content_id for which primary should be unset. + * @return TRUE on succes + */ + function clearPrimaryAttachment( $pContentId ) { + $ret = FALSE; + + if( @BitBase::verifyId( $pContentId )) { + $query = " + UPDATE `".BIT_DB_PREFIX."liberty_attachments` + SET `is_primary` = ? WHERE `content_id` = ?"; + $this->mDb->query( $query, array( NULL, $pContentId )); + $ret = TRUE; + } + + return $ret; + } + // }}} + + + /** + * === Attachment Preferences === + */ + + /** + * Returns the attachment preference value for the passed in key. + * + * @param string Hash key for the mPrefs value + * @param string Default value to return if the preference is empty + * @param int Optional content_id for arbitrary content preference + */ + function getAttachmentPreference( $pAttachmentId, $pPrefName, $pPrefDefault = NULL ) { + if( is_null( $this->mStoragePrefs ) ) { + $this->loadAttachmentPreferences(); + } + + $ret = NULL; + if( @BitBase::verifyId( $pAttachmentId ) && !empty( $pPrefName )) { + if( isset( $this->mStoragePrefs ) && isset( $this->mStoragePrefs[$pAttachmentId][$pPrefName] )) { + $ret = $this->mStoragePrefs[$pAttachmentId][$pPrefName]; + } else { + $ret = $pPrefDefault; + } + } + + return $ret; + } + + /** + * Returns the attachment preferences for a given attachment id + * + * @param string Hash key for the mPrefs value + * @param string Default value to return if the preference is empty + * @param int Optional content_id for arbitrary content preference + */ + function getAttachmentPreferences( $pAttachmentId ) { + global $gBitSystem; + + $ret = array(); + if( BitBase::verifyId( $pAttachmentId ) ) { + if( !empty( $this ) && is_subclass_of( $this, "LibertyMime" ) ) { + // we're loading from within object + if( is_null( $this->mStoragePrefs )) { + $this->loadAttachmentPreferences(); + } + + if( @BitBase::verifyId( $pAttachmentId ) && isset( $this->mStoragePrefs[$pAttachmentId] )) { + $ret = $this->mStoragePrefs[$pAttachmentId]; + } + } else { + // if the object isn't loaded, we need to get the prefs from the database + $sql = "SELECT `pref_name`, `pref_value` FROM `".BIT_DB_PREFIX."liberty_attachment_prefs` WHERE `attachment_id` = ?"; + $ret = $gBitSystem->mDb->getAssoc( $sql, array( (int)$pAttachmentId )); + } + } + + return $ret; + } + + /** + * setAttachmentPreference will set an attachment preferences without storing it in the database + * + * @param array $pAttachmentId + * @param array $pPrefName + * @param array $pPrefValue + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function setAttachmentPreference( $pAttachmentId, $pPrefName, $pPrefValue ) { + $this->mStoragePrefs[$pAttachmentId][$pPrefName] = $pPrefValue; + } + + /** + * Saves a preference to the liberty_content_prefs database table with the + * given pref name and value. If the value is NULL, the existing value will + * be delete and the value will not be saved. However, a zero will be + * stored. + * + * @param string Hash key for the prefs value + * @param string Value for the prefs hash key + */ + function storeAttachmentPreference( $pAttachmentId, $pPrefName, $pPrefValue = NULL ) { + global $gBitSystem; + $ret = FALSE; + if( @BitBase::verifyId( $pAttachmentId ) && !empty( $pPrefName )) { + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_attachment_prefs` WHERE `attachment_id` = ? AND `pref_name` = ?"; + $bindvars = array( $pAttachmentId, $pPrefName ); + $result = $gBitSystem->mDb->query( $query, $bindvars ); + if( !is_null( $pPrefValue )) { + $query = "INSERT INTO `".BIT_DB_PREFIX."liberty_attachment_prefs` (`attachment_id`,`pref_name`,`pref_value`) VALUES(?, ?, ?)"; + $bindvars[] = substr( $pPrefValue, 0, 250 ); + $result = $gBitSystem->mDb->query( $query, $bindvars ); + } + + // this function might be called statically + if( !empty( $this ) && $this->isValid() ) { + $this->mStoragePrefs[$pAttachmentId][$pPrefName] = $pPrefValue; + } + + $ret = TRUE; + } + return $ret; + } + + /** + * loadPreferences of the currently loaded object or pass in to get preferences of a specific content_id + * + * @param numeric $pContentId content_id of the item we want the prefs from (optional) + * @param numeric $pAttachmentId attachment_id of the item we want the prefs from (optional) + * @access public + * @return array of preferences if $pContentId or $pAttachmentId is set or pass preferences on to $this->mStoragePrefs + */ + function loadAttachmentPreferences( $pContentId = NULL ) { + global $gBitSystem; + + if( !@BitBase::verifyId( $pContentId ) && $this->isValid() && @BitBase::verifyId( $this->mContentId )) { + $pContentId = $this->mContentId; + $store_prefs = TRUE; + } + + $ret = array(); + if( !empty( $this ) && !is_null( $this->mStoragePrefs )) { + $ret = $this->mStoragePrefs; + } elseif( @BitBase::verifyId( $pContentId )) { + $sql = " + SELECT lap.`attachment_id`, lap.`pref_name`, lap.`pref_value` + FROM `".BIT_DB_PREFIX."liberty_attachment_prefs` lap + INNER JOIN `".BIT_DB_PREFIX."liberty_attachments` la ON (la.`attachment_id` = lap.`attachment_id`) + WHERE la.`content_id` = ?"; + $result = $gBitSystem->mDb->query( $sql, array( $pContentId )); + if( !empty( $result )) { + while( $aux = $result->fetchRow() ) { + $ret[$aux['attachment_id']][$aux['pref_name']] = $aux['pref_value']; + } + } + } + + // if neither a content id nor an attachment id are given, we will place the results in mStoragePrefs + if( !empty( $store_prefs )) { + $this->mStoragePrefs = $ret; + } else { + return $ret; + } + } + + /** + * expungeAttachmentPreferences will remove all attachment preferences of a given attachmtent + * + * @param array $pAttachmentId attachemnt we want to remove the prefs for + * @access public + * @return TRUE on success, FALSE on failure + */ + function expungeAttachmentPreferences( $pAttachmentId ) { + global $gBitSystem; + $ret = FALSE; + if( @BitBase::verifyId( $pAttachmentId ) ) { + $sql = "DELETE FROM `".BIT_DB_PREFIX."liberty_attachment_prefs` WHERE `attachment_id` = ?"; + $gBitSystem->mDb->query( $sql, array( $pAttachmentId )); + $ret = TRUE; + } + return $ret; + } + + public static function getAttachmentDownloadUrl( $pAttachmentId ) { + global $gBitSystem; + $ret = NULL; + if( BitBase::verifyId( $pAttachmentId ) ) { + if( $gBitSystem->isFeatureActive( "pretty_urls" ) || $gBitSystem->isFeatureActive( "pretty_urls_extended" )) { + $ret = LIBERTY_PKG_URL."download/file/".$pAttachmentId; + } else { + $ret = LIBERTY_PKG_URL."download_file.php?attachment_id=".$pAttachmentId; + } + } + return $ret; + } + + public function getDownloadUrl() { + $ret = ""; + if( $this->isValid() && $this->getField( 'attachment_id' ) ) { + $ret = LibertyMime::getAttachmentDownloadUrl( $this->getField( 'attachment_id' ) ); + } + return $ret; + } + + // {{{ =================== Meta Methods ==================== + /** + * storeMetaData + * + * @param numeric $pAttachmentId AttachmentID the data belongs to + * @param string $pType Type of data. e.g.: EXIF, ID3. This will default to "Meta Data" + * @param array $pStoreHash Data that needs to be stored in the database in an array. The key will be used as the meta_title. + * @access public + * @return TRUE on success, FALSE on failure + */ + public static function storeMetaData( $pAttachmentId, $pType = "Meta Data", $pStoreHash ) { + global $gBitSystem; + $ret = FALSE; + if( @BitBase::verifyId( $pAttachmentId ) && !empty( $pType ) && !empty( $pStoreHash )) { + if( is_array( $pStoreHash )) { + foreach( $pStoreHash as $key => $data ) { + if( !is_array( $data )) { + // store the data in the meta table + $meta = array( + 'attachment_id' => $pAttachmentId, + 'meta_type_id' => LibertyMime::storeMetaId( $pType, 'type' ), + 'meta_title_id' => LibertyMime::storeMetaId( $key, 'title' ), + ); + + // remove this entry from the database if it already exists + $gBitSystem->mDb->query( "DELETE FROM `".BIT_DB_PREFIX."liberty_attachment_meta_data` WHERE `attachment_id` = ? AND `meta_type_id` = ? AND `meta_title_id` = ?", $meta ); + + // don't insert empty lines + if( !empty( $data )) { + $meta['meta_value'] = $data; + $gBitSystem->mDb->associateInsert( BIT_DB_PREFIX."liberty_attachment_meta_data", $meta ); + } + + $ret = TRUE; + } else { + // should we recurse? + } + } + } + } + return $ret; + } + + /** + * storeMetaId + * + * @param string $pDescription Description of meta key. e.g.: Exif, ID3, Album, Artist + * @param string $pTable Table data is stored in - either 'type' or 'title' + * @access public + * @return newly stored ID on success, FALSE on failure + */ + private static function storeMetaId( $pDescription, $pTable = 'type' ) { + global $gBitSystem; + $ret = FALSE; + if( !empty( $pDescription )) { + if( !( $ret = LibertyMime::getMetaId( $pDescription, $pTable ))) { + $store = array( + "meta_{$pTable}_id" => $gBitSystem->mDb->GenID( "liberty_meta_{$pTable}s_id_seq" ), + "meta_{$pTable}" => LibertyMime::normalizeMetaDescription( $pDescription ), + ); + $gBitSystem->mDb->associateInsert( BIT_DB_PREFIX."liberty_meta_{$pTable}s", $store ); + $ret = $store["meta_{$pTable}_id"]; + } + } + return $ret; + } + + /** + * getMetaData + * + * @param numeric $pAttachmentId AttachmentID the data belongs to + * @param string $pType Type of data. e.g.: EXIF, ID3. + * @param string $pTitle Title of data. e.g.: Artist, Album. + * @access public + * @return array with meta data on success, FALSE on failure + * $note: Output format varies depending on requested data + */ + public static function getMetaData( $pAttachmentId, $pType = NULL, $pTitle = NULL ) { + global $gBitSystem; + $ret = array(); + if( @BitBase::verifyId( $pAttachmentId )) { + $bindVars = array( $pAttachmentId ); + $whereSql = ""; + if( !empty( $pType ) && !empty( $pTitle )) { + + // we have a type and title - only one entry will be returned + $bindVars[] = LibertyMime::normalizeMetaDescription( $pType ); + $bindVars[] = LibertyMime::normalizeMetaDescription( $pTitle ); + + $sql = " + SELECT lmd.`meta_value` + FROM `".BIT_DB_PREFIX."liberty_attachment_meta_data` lmd + INNER JOIN `".BIT_DB_PREFIX."liberty_meta_types` lmtype ON( lmd.`meta_type_id` = lmtype.`meta_type_id` ) + INNER JOIN `".BIT_DB_PREFIX."liberty_meta_titles` lmtitle ON( lmd.`meta_title_id` = lmtitle.`meta_title_id` ) + WHERE lmd.`attachment_id` = ? AND lmtype.`meta_type` = ? AND lmtitle.`meta_title` = ?"; + $ret = $gBitSystem->mDb->getOne( $sql, $bindVars ); + + } elseif( !empty( $pType )) { + + // only type given - return array with all vlues of this type + $bindVars[] = LibertyMime::normalizeMetaDescription( $pType ); + + $sql = " + SELECT lmtitle.`meta_title`, lmd.`meta_value` + FROM `".BIT_DB_PREFIX."liberty_attachment_meta_data` lmd + INNER JOIN `".BIT_DB_PREFIX."liberty_meta_types` lmtype ON( lmd.`meta_type_id` = lmtype.`meta_type_id` ) + INNER JOIN `".BIT_DB_PREFIX."liberty_meta_titles` lmtitle ON( lmd.`meta_title_id` = lmtitle.`meta_title_id` ) + WHERE lmd.`attachment_id` = ? AND lmtype.`meta_type` = ?"; + $ret = $gBitSystem->mDb->getAssoc( $sql, $bindVars ); + + } elseif( !empty( $pTitle )) { + + // only title given - return array with all vlues with this title + $bindVars[] = LibertyMime::normalizeMetaDescription( $pTitle ); + + $sql = " + SELECT lmtype.`meta_type`, lmd.`meta_value` + FROM `".BIT_DB_PREFIX."liberty_attachment_meta_data` lmd + INNER JOIN `".BIT_DB_PREFIX."liberty_meta_types` lmtype ON( lmd.`meta_type_id` = lmtype.`meta_type_id` ) + INNER JOIN `".BIT_DB_PREFIX."liberty_meta_titles` lmtitle ON( lmd.`meta_title_id` = lmtitle.`meta_title_id` ) + WHERE lmd.`attachment_id` = ? AND lmtitle.`meta_title` = ?"; + $ret = $gBitSystem->mDb->getAssoc( $sql, $bindVars ); + + } else { + + // nothing given - return nested array based on type and title + $sql = " + SELECT lmd.`attachment_id`, lmd.`meta_value`, lmtype.`meta_type`, lmtitle.`meta_title` + FROM `".BIT_DB_PREFIX."liberty_attachment_meta_data` lmd + INNER JOIN `".BIT_DB_PREFIX."liberty_meta_types` lmtype ON( lmd.`meta_type_id` = lmtype.`meta_type_id` ) + INNER JOIN `".BIT_DB_PREFIX."liberty_meta_titles` lmtitle ON( lmd.`meta_title_id` = lmtitle.`meta_title_id` ) + WHERE lmd.`attachment_id` = ?"; + + $result = $gBitSystem->mDb->query( $sql, $bindVars ); + while( $aux = $result->fetchRow() ) { + $ret[$aux['meta_type']][$aux['meta_title']] = $aux['meta_value']; + } + } + } + return $ret; + } + + /** + * expungeMetaData will remove the meta data for a given attachment + * + * @param array $pAttachmentId Attachment ID of attachment + * @access public + * @return query result + */ + function expungeMetaData( $pAttachmentId ) { + global $gBitSystem; + if( @BitBase::verifyId( $pAttachmentId )) { + return $gBitSystem->mDb->query( "DELETE FROM `".BIT_DB_PREFIX."liberty_attachment_meta_data` WHERE `attachment_id` = ?", array( $pAttachmentId )); + } + } + + /** + * getMetaId + * + * @param string $pDescription Description of meta key. e.g.: Exif, ID3, Album, Artist + * @param string $pTable Table data is stored in - either 'type' or 'title' + * @access public + * @return meta type or title id on sucess, FALSE on failure + */ + private static function getMetaId( $pDescription, $pTable = 'type' ) { + global $gBitSystem; + $ret = FALSE; + if( !empty( $pDescription ) && ( $pTable == 'type' || $pTable == 'title' )) { + $ret = $gBitSystem->mDb->getOne( "SELECT `meta_{$pTable}_id` FROM `".BIT_DB_PREFIX."liberty_meta_{$pTable}s` WHERE `meta_{$pTable}` = ?", array( LibertyMime::normalizeMetaDescription( $pDescription ))); + } + return $ret; + } + + /** + * getMetaDescription + * + * @param string $pId ID of type or title we want the description for + * @param string $pTable Table data is stored in - either 'type' or 'title' + * @access public + * @return description on sucess, FALSE on failure + */ + function getMetaDescription( $pId, $pTable = 'type' ) { + global $gBitSystem; + $ret = FALSE; + if( @BitBase::verifyId( $pId )) { + $ret = $gBitSystem->mDb->getOne( "SELECT `meta_{$pTable}` FROM `".BIT_DB_PREFIX."liberty_meta_{$pTable}s` WHERE `meta_{$pTable}_id` = ?", array( $pId )); + } + return $ret; + } + + /** + * normalizeMetaDescription + * + * @param string $pDescription Description of meta key. e.g.: Exif, ID3, Album, Artist + * @access public + * @return normalized meta description that can be used as a guid + */ + public static function normalizeMetaDescription( $pDescription ) { + return strtolower( substr( preg_replace( "![^a-zA-Z0-9]!", "", trim( $pDescription )), 0, 250 )); + } + // }}} +} + +/** + * mime_get_storage_sub_dir_name get a filename based on the uploaded file + * + * @param array $pFileHash File information provided in $_FILES + * @access public + * @return appropriate sub dir name + */ +if( !function_exists( 'liberty_mime_get_storage_sub_dir_name' )) { + function liberty_mime_get_storage_sub_dir_name( $pFileHash = NULL ) { + if( !empty( $pFileHash['type'] ) ) { + // type is from upload file hash + $mimeType = $pFileHash['type']; + } elseif( !empty( $pFileHash['mime_type'] ) ) { + // mime_type is from liberty_files + $mimeType = $pFileHash['mime_type']; + } + + if( !empty( $mimeType ) && strstr( $mimeType, "/" )) { + $ret = strtolower( preg_replace( "!/.*$!", "", $mimeType )); + // if we only got 'application' we will use the file extenstion + if( $ret == 'application' && !empty( $pFileHash['name'] ) && ( $pos = strrpos( $pFileHash['name'], "." )) !== FALSE ) { + $ret = strtolower( substr( $pFileHash['name'], $pos + 1 )); + } + } + + // append an 's' to not create an image and images dir side by side (legacy reasons) + if( empty( $ret ) || $ret == 'image' ) { + $ret = 'images'; + } + return $ret; + } +} + +/** + * liberty_mime_get_storage_branch - get url to store files for the feature site_upload_dir. It creates a calculable hierarchy of directories + * + * @access public + * @author Christian Fowler<spider@steelsun.com> + * @param $pParamHash key=>value pairs to determine path. Possible keys in descending directory depth are: 'user_id' indicates the 'users/.../<user_id>' branch or use the 'common' branch if null, 'package' - any desired directory below the StoragePath. this will be created if it doesn't exist, 'sub_dir' - the sub-directory in the package organization directory, this is often a primary id such as attachment_id + * @return string full path on local filsystem to store files. + */ +if( !function_exists( 'liberty_mime_get_storage_branch' )) { + function liberty_mime_get_storage_branch( $pParamHash ) { + // *PRIVATE FUNCTION. GO AWAY! DO NOT CALL DIRECTLY!!! + global $gBitSystem; + $pathParts = array(); + + + if( $pUserId = BitBase::getParameter( $pParamHash, 'user_id' ) ) { + $pathParts[] = 'users'; + $pathParts[] = (int)($pUserId % 1000); + $pathParts[] = $pUserId; + } elseif( $pAttachmentId = BitBase::getParameter( $pParamHash, 'attachment_id' ) ) { + $pathParts[] = 'attachments'; + $pathParts[] = (int)($pAttachmentId % 1000); + $pathParts[] = $pAttachmentId; + } else { + $pathParts[] = 'common'; + } + + if( $pPackage = BitBase::getParameter( $pParamHash, 'package' ) ) { + $pathParts[] = $pPackage; + } + // In case $pSubDir is multiple levels deep we'll need to mkdir each directory if they don't exist + if( $pSubDir = BitBase::getParameter( $pParamHash, 'sub_dir' ) ){ + $pSubDirParts = preg_split('#/#',$pSubDir); + foreach ($pSubDirParts as $subDir) { + $pathParts[] = $subDir; + } + } else { + $pSubDir = liberty_mime_get_storage_sub_dir_name( $pParamHash ); + } + + $fullPath = implode( '/', $pathParts ).'/'; + if( BitBase::getParameter( $pParamHash, 'create_dir', TRUE ) ){ + if( !file_exists( STORAGE_PKG_PATH.$fullPath ) ) { + mkdir_p( STORAGE_PKG_PATH.$fullPath ); + } + } + + return $fullPath; + } +} + +if( !function_exists( 'liberty_mime_get_storage_url' )) { + function liberty_mime_get_storage_url( $pParamHash ) { + return STORAGE_PKG_URL.liberty_mime_get_storage_branch( $pParamHash ); + } +} + +if( !function_exists( 'liberty_mime_get_storage_path' )) { + function liberty_mime_get_storage_path( $pParamHash ) { + return STORAGE_PKG_PATH.liberty_mime_get_storage_branch( $pParamHash ); + } +} + +if( !function_exists( 'liberty_mime_get_source_url' )) { + function liberty_mime_get_source_url( $pParamHash ) { + $fileName = BitBase::getParameter( $pParamHash, 'file_name' ); + if( empty( $pParamHash['package'] ) ) { + $pParamHash['package'] = liberty_mime_get_storage_sub_dir_name( array( 'mime_type' => BitBase::getParameter( $pParamHash, 'mime_type' ), 'name' => $fileName ) ); + } + if( empty( $pParamHash['sub_dir'] ) ) { + $pParamHash['sub_dir'] = BitBase::getParameter( $pParamHash, 'attachment_id' ); + } + $defaultFileName = liberty_mime_get_default_file_name( $fileName, $pParamHash['mime_type'] ); + $fileBranch = liberty_mime_get_storage_branch( $pParamHash ); + if( file_exists( STORAGE_PKG_PATH.$fileBranch.$defaultFileName ) ) { + $ret = STORAGE_PKG_URL.$fileBranch.$defaultFileName; + } else { + $ret = STORAGE_PKG_URL.$fileBranch.basename( BitBase::getParameter( $pParamHash, 'file_name' ) ); + } + return $ret; + } +} + +if( !function_exists( 'liberty_mime_get_source_file' )) { + function liberty_mime_get_source_file( $pParamHash ) { + $fileName = BitBase::getParameter( $pParamHash, 'file_name' ); + if( empty( $pParamHash['package'] ) ) { + $pParamHash['package'] = liberty_mime_get_storage_sub_dir_name( array( 'mime_type' => BitBase::getParameter( $pParamHash, 'mime_type' ), 'name' => $fileName ) ); + } + if( empty( $pParamHash['sub_dir'] ) ) { + $pParamHash['sub_dir'] = BitBase::getParameter( $pParamHash, 'attachment_id' ); + } + $defaultFileName = liberty_mime_get_default_file_name( $fileName, $pParamHash['mime_type'] ); + $ret = STORAGE_PKG_PATH.liberty_mime_get_storage_branch( $pParamHash ).$defaultFileName; + if( !file_exists( $ret ) ) { + $ret = STORAGE_PKG_PATH.liberty_mime_get_storage_branch( $pParamHash ).basename( BitBase::getParameter( $pParamHash, 'file_name' ) ); + } + return $ret; + } +} + +if( !function_exists( 'liberty_mime_get_default_file_name' )) { + function liberty_mime_get_default_file_name( $pFileName, $pMimeType ) { + global $gBitSystem; + if( $gBitSystem->isFeatureActive( 'liberty_originalize_file_names' ) ) { + $ret = 'original.'.$gBitSystem->getMimeExtension( $pMimeType ); + } else { + $ret = $pFileName; + } + return $ret; + } +} + +/* vim: :set fdm=marker : */ + +?> diff --git a/includes/classes/LibertyStructure.php b/includes/classes/LibertyStructure.php new file mode 100644 index 0000000..7cb5755 --- /dev/null +++ b/includes/classes/LibertyStructure.php @@ -0,0 +1,1153 @@ +<?php +/** + * Management of Liberty Content + * + * @package liberty + * @author spider <spider@steelsun.com> + */ + +/** + * required setup + */ +require_once( LIBERTY_PKG_CLASS_PATH.'LibertyBase.php' ); + +/** + * System class for handling the liberty package + * + * @package liberty + */ +class LibertyStructure extends LibertyBase { + public $mStructureId; + + function __construct( $pStructureId=NULL, $pContentId=NULL ) { + // we need to init our database connection early + parent::__construct(); + $this->mStructureId = $pStructureId; + $this->mContentId = $pContentId; + $this->load(); + } + + function load( $pContentId=NULL ) { + if( $this->mStructureId || $this->mContentId ) { + if( $this->mInfo = $this->getNode( $this->mStructureId, $this->mContentId ) ) { + global $gLibertySystem; + $this->mStructureId = $this->mInfo['structure_id']; + $this->mContentId = $this->mInfo['content_id']; + $this->mInfo['content_type'] = $gLibertySystem->mContentTypes[$this->mInfo['content_type_guid']]; + } + } + return( $this->mInfo && count( $this->mInfo ) ); + } + + /** + * get the details to a given node + * + * @param array $pStructureId Structure ID of the node + * @param array $pContentId Content ID of the node + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getNode( $pStructureId=NULL, $pContentId=NULL ) { + global $gLibertySystem, $gBitSystem; + static $sStructureNodeCache; + $contentTypes = $gLibertySystem->mContentTypes; + + if( @$this->verifyId( $pStructureId ) ) { + if (!empty($sStructureNodeCache['structure_id'][$pStructureId])) { + return $sStructureNodeCache['structure_id'][$pStructureId]; + } + $where = ' WHERE ls.`structure_id`=?'; + $bindVars = array( $pStructureId ); + } elseif( @$this->verifyId( $pContentId ) ) { + if (!empty($sStructureNodeCache['content_id'][$pContentId])) { + return $sStructureNodeCache['content_id'][$pContentId]; + } + $where = ' WHERE ls.`content_id`=?'; + $bindVars = array( $pContentId ); + } + + $ret = NULL; + $query = 'SELECT ls.*, lc.`user_id`, lc.`title`, lc.`content_type_guid`, uu.`login`, uu.`real_name` + FROM `'.BIT_DB_PREFIX.'liberty_structures` ls + INNER JOIN `'.BIT_DB_PREFIX.'liberty_content` lc ON (ls.`content_id`=lc.`content_id`) + LEFT JOIN `'.BIT_DB_PREFIX.'users_users` uu ON ( uu.`user_id` = lc.`user_id` )' . $where; + + if( $result = $this->mDb->query( $query, $bindVars ) ) { + $ret = $result->fetchRow(); + } + + if( !empty( $contentTypes[$ret['content_type_guid']] ) ) { + // quick alias for code readability + $type = &$contentTypes[$ret['content_type_guid']]; + if( empty( $type['content_object'] ) && !empty( $gBitSystem->mPackages[$type['handler_package']] ) ) { + // create *one* object for each object *type* to call virtual methods. + $handlerFile = $gBitSystem->mPackages[$type['handler_package']]['path'].$type['handler_file']; + if( file_exists( $handlerFile ) ) { + include_once( $handlerFile ); + if( class_exists( $type['handler_class'] ) ) { + $type['content_object'] = new $type['handler_class'](); + } + } + } + if( !empty( $type['content_object'] ) && is_object( $type['content_object'] ) ) { + $ret['title'] = $type['content_object']->getTitleFromHash( $ret ); + } + } + + $sStructureNodeCache['structure_id'][$ret['structure_id']] = $ret; + $sStructureNodeCache['content_id'][$ret['content_id']] = $ret; + + return $ret; + } + + function hasViewPermission( $pVerifyAccessControl=TRUE ) { + $ret = FALSE; + if( !empty( $this->mInfo['content_object'] ) && is_a( $this->mInfo['content_object'], 'LibertyContent' ) ) { + return( $this->mInfo['content_object']->hasUpdatePermission( $pVerifyAccessControl ) || empty( $this->mInfo['content_object']->mViewContentPerm ) || $this->mInfo['content_object']->hasUserPermission( $this->mInfo['content_object']->mViewContentPerm, $pVerifyAccessControl )); + } + return $ret; + } + + /** + * Check if a node is a root node + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function isRootNode() { + $ret = FALSE; + if( @$this->verifyId( $this->mInfo['structure_id'] ) ) { + $ret = $this->mInfo['root_structure_id'] == $this->mInfo['structure_id']; + } + return $ret; + } + + /** + * get the title of the root node + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getRootObject() { + $ret = NULL; + if( !empty( $this->mInfo['root_structure_id'] ) ) { + if( $rootHash = $this->mDb->getRow( "SELECT * FROM `".BIT_DB_PREFIX."liberty_structures` WHERE `structure_id` = ?", array( $this->mInfo['root_structure_id'] ) ) ) { + $ret = $this->getLibertyObject( $rootHash['content_id'] ); + } + } + return $ret; + } + + /** + * get the title of the root node + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getRootTitle() { + $ret = NULL; + if( isset( $this->mInfo['structure_path'][0]['title'] ) ) { + $ret = $this->mInfo['structure_path'][0]['title']; + } + return $ret; + } + + /** + * if you only have a structure id and you want to figure out the root structure id, use this + * + * @param array $pParamHash['structure_id'] is the structure id from which you want to figure out the root structure id + * @access public + * @return none. updates $pParamHash['root_structure_id'] by reference + */ + function getRootStructureId( &$pParamHash ) { + if( @BitBase::verifyId( $pParamHash['root_structure_id'] ) ) { + $pParamHash['root_structure_id'] = $pParamHash['root_structure_id']; + } elseif( @BitBase::verifyId( $this->mInfo['root_structure_id'] ) ) { + $pParamHash['root_structure_id'] = $this->mInfo['root_structure_id']; + } elseif( @BitBase::verifyId( $pParamHash['structure_id'] ) ) { + $pParamHash['root_structure_id'] = $this->mDb->getOne( "SELECT `root_structure_id` FROM `".BIT_DB_PREFIX."liberty_structures` WHERE `structure_id` = ?", array( $pParamHash['structure_id'] ) ); + } else { + $pParamHash['root_structure_id'] = NULL; + } + } + + // This is a utility function mainly used for upgrading sites. + function setTreeRoot( $pRootId, $pTree ) { + foreach( $pTree as $structRow ) { + $this->mDb->query( "UPDATE `".BIT_DB_PREFIX."liberty_structures` SET `root_structure_id`=? WHERE `structure_id`=?", array( $pRootId, $structRow["structure_id"] ) ); + if( !empty( $structRow["sub"] ) ) { + $this->setTreeRoot( $pRootId, $structRow["sub"] ); + } + } + } + + + function isValid() { + return( $this->verifyId( $this->mStructureId ) ); + } + + /** + * loadNavigation + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function loadNavigation() { + if( $this->isValid() ) { + $this->mInfo["prev"] = null; + // Get structure info for this page + if( !$this->isRootNode() && ($prev_structure_id = $this->getPrevStructureNode( $this->mStructureId )) ) { + $this->mInfo["prev"] = $this->getNode($prev_structure_id); + } + $next_structure_id = $this->getNextStructureNode( $this->mStructureId ); + $this->mInfo["next"] = null; + if (isset($next_structure_id)) { + $this->mInfo["next"] = $this->getNode( $next_structure_id) ; + } + $this->mInfo["parent"] = $this->getStructureParentInfo( $this->mStructureId ); + $this->mInfo["home"] = $this->getNode( $this->mStructureId ); + } + return TRUE; + } + + function loadPath() { + if( $this->isValid() ) { + $this->mInfo['structure_path'] = $this->getPath( $this->mStructureId ); + } + return( !empty( $this->mInfo['structure_path'] ) ); + } + + /** + * This can be used to construct a path from the structure head to the requested page. + * @returns an array of page_info arrays. + */ + function getPath( $pStructureId ) { + $structure_path = array(); + $page_info = $this->getNode($pStructureId); + + if ($page_info["parent_id"]) { + $structure_path = $this->getPath($page_info["parent_id"]); + } + $structure_path[] = $page_info; + return $structure_path; + } + + /** + * Get full structure from database + * @param $pStructureId structure for which we want structure + * @return full structure + */ + function getStructure( &$pParamHash ) { + global $gBitSystem, $gLibertySystem; + // make sure we have the correct id to get the entire structure + LibertyStructure::getRootStructureId( $pParamHash ); + + $ret = FALSE; + + if( @BitBase::verifyId( $pParamHash['root_structure_id'] ) ) { + // Get all nodes for this structure + $query = "SELECT ls.*, lc.`user_id`, lc.`title`, lc.`content_type_guid`, uu.`login`, uu.`real_name` + FROM `".BIT_DB_PREFIX."liberty_structures` ls + INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON ( ls.`content_id` = lc.`content_id` ) + INNER JOIN `".BIT_DB_PREFIX."users_users` uu ON ( uu.`user_id` = lc.`user_id` ) + WHERE ls.`root_structure_id` = ? ORDER BY `pos` ASC"; + $result = $this->mDb->query( $query, array( $pParamHash['root_structure_id'] ) ); + + $subs = array(); + $row_max = $result->numRows(); + $contentTypes = $gLibertySystem->mContentTypes; + while( $res = $result->fetchRow() ) { + $aux = array(); + $aux = $res; + if( !empty( $contentTypes[$res['content_type_guid']] ) ) { + // quick alias for code readability + $type = &$contentTypes[$res['content_type_guid']]; + if( empty( $type['content_object'] ) ) { + // create *one* object for each object *type* to call virtual methods. + $handlerFile = $gBitSystem->mPackages[$type['handler_package']]['path'].$type['handler_file']; + if( file_exists( $handlerFile ) ) { + include_once( $handlerFile ); + if( class_exists( $type['handler_class'] ) ) { + $type['content_object'] = new $type['handler_class'](); + } + } + } + if( !empty( $pParamHash['thumbnail_size'] ) ) { + $aux['content_object'] = new $type['handler_class']( NULL, $aux['content_id'] ); + if( $aux['content_object']->load() ) { + $aux['thumbnail_url'] = $aux['content_object']->getThumbnailUrl( $pParamHash['thumbnail_size'] ); + } + } + if( !empty( $type['content_object'] ) && is_object( $type['content_object'] ) ) { + $aux['title'] = $type['content_object']->getTitleFromHash( $aux ); + } + $ret[$aux['structure_id']] = $aux; + } + } + } + return $ret; + } + + /** + * Get all structures in $pStructureHash that have a given parent_id + * @param $pStructureHash full menu as supplied by '$this->getItemList( $pMenuId );' + * @return array of nodes with a given parent_id + */ + function getChildNodes( $pStructureHash, $pParentId = 0 ) { + $ret = array(); + if( !empty( $pStructureHash ) ) { + foreach( $pStructureHash as $node ) { + if( $node['parent_id'] == $pParentId ) { + $ret[] = $node; + } + } + } + return $ret; + } + + /** + * Create a usable array from the data in the database from getStructure() + * @param $pStructureHash raw structure data from database + * @return nicely formatted and cleaned up structure array + */ + function createSubTree( $pStructureHash, $pParentId = 0, $pParentPos = '', $pLevel = 0 ) { + $ret = array(); + // get all child menu Nodes for this structure_id + $children = LibertyStructure::getChildNodes( $pStructureHash, $pParentId ); + $pos = 1; + $row_max = count( $children ); + + // we need to insert the root structure item first + if (!$pLevel && !empty($pStructureHash)) { + foreach( $pStructureHash as $node ) { + if( ( $pParentId == 0 && $node['structure_id'] == $node['root_structure_id'] ) || $node['structure_id'] == $pParentId) { + $aux = $node; + $aux["first"] = true; + $aux["last"] = true; + $aux["pos"] = ''; + $aux["level"] = $pLevel++; + $ret[] = $aux; + } + } + } + + foreach( $children as $node ) { + $aux = $node; + $aux['level'] = $pLevel; + $aux['first'] = ( $pos == 1 ); + $aux['last'] = FALSE; + $aux['has_children'] = FALSE; + if( strlen( $pParentPos ) == 0 ) { + $aux["pos"] = "$pos"; + } else { + $aux["pos"] = $pParentPos . '.' . "$pos"; + } + $ret[] = $aux; + //Recursively add any children + $subs = LibertyStructure::createSubTree( $pStructureHash, $node['structure_id'], $aux['pos'], ( $pLevel + 1 ) ); + if( !empty( $subs ) ) { + $r = array_pop( $ret ); + $r['has_children'] = TRUE; + array_push( $ret, $r ); + $ret = array_merge( $ret, $subs ); + } + + if( $pos == $row_max ) { + $aux['structure_id'] = $node['structure_id']; + $aux['first'] = FALSE; + $aux['last'] = TRUE; + $ret[] = $aux; + } + $pos++; + } + return $ret; + } + + // get sub tree of $pStructureId + function getSubTree( $pStructureId, $pRootTree = FALSE, $pListHash=NULL ) { + global $gLibertySystem, $gBitSystem; + $ret = array(); + if( @BitBase::verifyId( $pStructureId ) ) { + $pListHash['structure_id'] = $pStructureId; + $structureHash = $this->getStructure( $pListHash ); + $ret = $this->createSubTree( $structureHash, ( ( $pRootTree ) ? $pListHash['root_structure_id'] : $pStructureId ) ); + } + return $ret; + } + + /** + * getList + * + * @param array $pListHash + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getList( &$pListHash ) { + global $gBitSystem, $gBitUser; + + BitBase::prepGetList( $pListHash ); + + if( !empty( $pListHash['find'] ) ) { + $findesc = '%' . $pListHash['find'] . '%'; + $mid = " (`parent_id` is null or `parent_id`=0) and (lc.`title` like ?)"; + $bindVars=array($findesc); + } else { + $mid = " (`parent_id` is null or `parent_id`=0) "; + $bindVars=array(); + } + + if( @$this->verifyId( $pListHash['user_id'] ) ) { + $mid .= " AND lc.`user_id` = ? "; + array_push( $bindVars, $pListHash['user_id'] ); + } + + if( empty( $pListHash['sort_mode'] ) ) { + $pListHash['sort_mode'] = 'last_modified_desc'; + } + + if( !empty( $pListHash['content_type_guid'] ) ) { + $mid .= " AND lc.`content_type_guid`=? "; + array_push( $bindVars, $pListHash['content_type_guid'] ); + } + $query = "SELECT ls.`structure_id`, ls.`parent_id`, ls.`content_id`, `page_alias`, `pos`, lc.`title`, `data`, `last_modified`, lc.`modifier_user_id`, `ip`, lc.`user_id` AS `creator_user_id`, uu.`login` AS `creator_user`, uu.`real_name` , uu.`email` + FROM `".BIT_DB_PREFIX."liberty_structures` ls INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON ( ls.`content_id` = lc.`content_id` ) INNER JOIN `".BIT_DB_PREFIX."users_users` uu ON ( lc.`user_id` = uu.`user_id` ) + WHERE $mid + ORDER BY ".$this->mDb->convertSortmode($pListHash['sort_mode']); + $query_cant = "SELECT count(*) + FROM `".BIT_DB_PREFIX."liberty_structures` ls INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON ( ls.`content_id` = lc.`content_id` ) + WHERE $mid"; + $result = $this->mDb->query($query,$bindVars,$pListHash['max_records'],$pListHash['offset']); + $cant = $this->mDb->getOne($query_cant,$bindVars); + $ret = array(); + + while ($res = $result->fetchRow()) { + if( $gBitSystem->isPackageActive( 'bithelp' ) && file_exists(BITHELP_PKG_PATH.$res['title'].'/index.html')) { + $res['webhelp']='y'; + } else { + $res['webhelp']='n'; + } + $ret[] = $res; + } + + $retval = array(); + $retval["data"] = $ret; + $retval["cant"] = $cant; + return $retval; + } + + /** + * clean up and prepare a complete structure in the form of arrays about to be stored + * @param $pParamHash is a set of arrays generated by the DynamicTree javascript tree builder + * @return TRUE on success, FALSE on failure where $this->mErrors will contain the reason why it failed + */ + function verifyStructure( &$pParamHash ) { + $storeNodes = array(); + if( !static::getParameter( $pParamHash, 'root_structure_id' ) ) { + $pParamHash['root_structure_id'] = $this->getField( 'root_structure_id' ); + } + + if( !static::verifyId( $pParamHash['root_structure_id'] ) ) { + $this->mErrors['verify_structure'] = tra( "Unknown root structure." ); + } else { + if( !empty( $pParamHash['structure_json'] ) ) { +//vd( $pParamHash['structure_json'] ); + // {{{ closure here to recursively parse json + $parseStructureJson = function($treeHash, $pRootId, $pParentId, $pLevel, $pPos ) use ( &$parseStructureJson, &$storeNodes ) { + if( is_numeric( key( $treeHash ) ) ) { + $pos = 1; + foreach( array_keys( $treeHash ) as $i ) { + // Array of nodes came in. Process each at the same level + $parseStructureJson( $treeHash[$i], $pRootId, $pParentId, $pLevel, $pos++ ); + } + } else { + // Base node with data + $storeNode = array(); + $storeNode['root_structure_id'] = $pRootId; + $storeNode['parent_id'] = $pParentId; + $storeNode['structure_id'] = $treeHash['structure_id']; + $storeNode['content_id'] = $treeHash['content_id']; + $storeNode['pos'] = $pPos++; + //$storeNode['page_alias'] + $storeNode['structure_level'] = $pLevel; + $storeNodes[$treeHash['structure_id']] = $storeNode; + if( !empty( $treeHash['children'] ) ) { + // current node has children, recurse down in + $parseStructureJson( $treeHash['children'], $pRootId, $treeHash['structure_id'], $pLevel + 1, 1 ); + } + } + }; + // }}} + + $parseStructureJson( $pParamHash['structure_json'], $this->mStructureId, $pParamHash['root_structure_id'], 1, 1 ); + if( !empty( $storeNodes ) ) { + $pParamHash['structure_store'] = $storeNodes; + } +//vd( $storeNodes ); + } + + + // deprecated flat tree store, code is unused AFAIK.-- spiderr + if( !empty( $pParamHash['structure'] ) ) { + // LibertyStructure::embellishStructureHash( $pParamHash['structure'] ); + // $structureHash = LibertyStructure::flattenStructureHash( $pParamHash['structure'] ); + + // replace the 'tree' in the data array with the root_structure_id + foreach( $pParamHash['data'] as $structure_id => $node ) { + if( !@BitBase::verifyId( $pParamHash['data'][$structure_id]['parent_id'] ) ) { + $pParamHash['data'][$structure_id]['parent_id'] = $pParamHash['root_structure_id']; + } + } + + foreach( $structureHash as $node ) { + if( @BitBase::verifyId( $node['structure_id'] ) ) { + $pParamHash['structure_store'][$node['structure_id']] = array_merge( $node, $pParamHash['data'][$node['structure_id']] ); + $pParamHash['structure_store'][$node['structure_id']]['root_structure_id'] = $pParamHash['root_structure_id']; + } + } + } + } + + if( empty( $pParamHash['structure_store'] ) ) { + $this->mErrors['verify_structure'] = tra( "There are no changes to save." ); + } + + // clear up some memory + if( !empty( $pParamHash['structure_json'] ) ) { unset( $pParamHash['structure_json'] ); } + if( !empty( $pParamHash['structure'] ) ) { unset( $pParamHash['structure'] ); } + if( !empty( $pParamHash['data'] ) ) { unset( $pParamHash['data'] ); } + + return( count( $this->mErrors ) == 0 ); + } + + /** + * store a complete structure where ever subarray contains a complete node as it should go into the database + * @param $pParamHash is an array with subarrays, each representing a structure node ready to associativley inserted into the database + * @return TRUE on success, FALSE on failure where $this->mErrors will contain the reason why it failed + */ + function storeStructure( $pParamHash ) { + if( $this->verifyStructure( $pParamHash ) ) { + // now that the structure is ready to be stored, we remove the old structure first and then insert the new one. + $this->StartTrans(); + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_structures` WHERE `root_structure_id`=? AND `structure_id`!=?"; + $result = $this->mDb->query( $query, array( (int)$pParamHash['root_structure_id'], (int)$pParamHash['root_structure_id'] ) ); + foreach( $pParamHash['structure_store'] as $node ) { + $this->mDb->associateInsert( BIT_DB_PREFIX."liberty_structures", $node ); + } + $this->CompleteTrans(); + } + return( count( $this->mErrors ) == 0 ); + } + + /** + * make sure the array only contains one level depth + * @param $pParamHash contains a nested set of arrays with structure_id and pos values set + * @return flattened array + */ + function flattenStructureHash( $pParamHash, $i = -10000 ) { + $ret = array(); + foreach( $pParamHash as $key => $node ) { + if( !empty( $node ) && count( $node ) > 2 ) { + $ret = array_merge( $ret, LibertyStructure::flattenStructureHash( $node, $i ) ); + $i++; + } elseif( count( $node ) == 2 ) { + $ret[] = $node; + $i++; + } else { + $ret[$i][$key] = $node; + } + } + return $ret; + } + + /** + * cleans up and reorganises data in nested array where keys are structure_id + * @param $pParamHash contains a nested set of arrays with structure_id as key + * @return reorganised array + */ + function embellishStructureHash( &$pParamHash ) { + $pos = 1; + foreach( $pParamHash as $structure_id => $node ) { + if( !empty( $node ) ) { + LibertyStructure::embellishStructureHash( $node ); + } + $node['pos'] = $pos++; + $node['structure_id'] = $structure_id; + $pParamHash[$structure_id] = $node; + } + } + + /** + * prepare a structure node for storage in the database + * @param $pParamHash contains various settings for the node to be stored + * @return TRUE on success, FALSE on failure where $this->mErrors will contain the reason why it failed + */ + function verifyNode( &$pParamHash ) { + if( !@$this->verifyId( $pParamHash['content_id'] ) ) { + $this->mErrors['content'] = 'Could not store structure. Invalid content id. '.$pParamHash['content_id']; + } else { + if( !@$this->verifyId( $pParamHash['parent_id'] ) ) { + $pParamHash['parent_id'] = 0; + } + if( empty( $pParamHash['alias'] ) ) { + $pParamHash['alias'] = ''; + } + if( isset( $pParamHash['after_ref_id'] ) ) { + $pParamHash['max'] = $this->mDb->getOne("select `pos` from `".BIT_DB_PREFIX."liberty_structures` where `structure_id`=?",array((int)$pParamHash['after_ref_id'])); + } else { + $pParamHash['max'] = $this->mDb->getOne("select max(`pos`) from `".BIT_DB_PREFIX."liberty_structures` where `parent_id`=?",array((int)$pParamHash['parent_id'])); + } + if( $pParamHash['max'] > 0 ) { + // For example, if max is 5 then we are inserting after position 5 so we'll insert 5 and move all the others + $query = "update `".BIT_DB_PREFIX."liberty_structures` set `pos`=`pos`+1 where `pos`>? and `parent_id`=?"; + $result = $this->mDb->query($query,array((int)$pParamHash['max'], (int)$pParamHash['parent_id'])); + } + $pParamHash['max']++; + + if( $pParamHash['structure_id'] = $this->mDb->getOne( "SELECT `structure_id` FROM `".BIT_DB_PREFIX."liberty_structures` WHERE `root_structure_id`=? and `content_id`=?", array($pParamHash['root_structure_id'], $pParamHash['content_id'] ) ) ) { + $this->mErrors[] = tra( 'Content already exists in structure.' )." ($pParamHash[structure_id])"; + } + } + return( count( $this->mErrors ) == 0 ); + } + + /** Create a structure entry with the given name + * @param parent_id The parent entry to add this to. If NULL, create new structure. + * @param after_ref_id The entry to add this one after. If NULL, put it in position 0. + * @param name The wiki page to reference + * @param alias An alias for the wiki page name. + * @return the new entries structure_id or null if not created. + */ + function storeNode( &$pParamHash ) { + global $gBitSystem; + $ret = null; + // If the page doesn't exist then create a new wiki page! + $now = $gBitSystem->getUTCTime(); +// $created = $this->create_page($name, 0, '', $now, tra('created from structure'), 'system', '0.0.0.0', ''); + // if were not trying to add a duplicate structure head + if ( $this->verifyNode( $pParamHash ) ) { + $this->StartTrans(); + + //Create a new structure entry + $pParamHash['structure_id'] = $this->mDb->GenID( 'liberty_structures_id_seq' ); + if( !@$this->verifyId( $pParamHash['root_structure_id'] ) ) { + $pParamHash['root_structure_id'] = $pParamHash['structure_id']; + } + $query = "INSERT INTO `".BIT_DB_PREFIX."liberty_structures`( `structure_id`, `parent_id`,`content_id`, `root_structure_id`, `page_alias`, `pos` ) values(?,?,?,?,?,?)"; + $result = $this->mDb->query( $query, array( $pParamHash['structure_id'], $pParamHash['parent_id'], (int)$pParamHash['content_id'], (int)$pParamHash['root_structure_id'], $pParamHash['alias'], $pParamHash['max'] ) ); + $this->CompleteTrans(); + $ret = $pParamHash['structure_id']; + } else { + //vd( $this->mErrors ); + } + return $ret; + } + + /** + * moveNodeWest + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function moveNodeWest() { + if( $this->isValid() ) { + //If there is a parent and the parent isnt the structure root node. + $this->StartTrans(); + if( @$this->verifyId( $this->mInfo["parent_id"] ) ) { + $parentNode = $this->getNode( $this->mInfo["parent_id"] ); + if( @$this->verifyId( $parentNode['parent_id'] ) ) { + //Make a space for the node after its parent + $query = "update `".BIT_DB_PREFIX."liberty_structures` set `pos`=`pos`+1 where `pos`>? and `parent_id`=?"; + $this->mDb->query( $query, array( $parentNode['pos'], $parentNode['parent_id'] ) ); + //Move the node up one level + $query = "update `".BIT_DB_PREFIX."liberty_structures` set `parent_id`=?, `pos`=(? + 1) where `structure_id`=?"; + $this->mDb->query($query, array( $parentNode['parent_id'], $parentNode['pos'], $this->mStructureId ) ); + } + } + $this->CompleteTrans(); + } + } + + /** + * moveNodeEast + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function moveNodeEast() { + if( $this->isValid() ) { + $this->StartTrans(); + $query = "select `structure_id`, `pos` from `".BIT_DB_PREFIX."liberty_structures` where `pos`<? and `parent_id`=? order by `pos` desc"; + $result = $this->mDb->query($query,array($this->mInfo["pos"], (int)$this->mInfo["parent_id"])); + if ($previous = $result->fetchRow()) { + //Get last child nodes for previous sibling + $query = "select `pos` from `".BIT_DB_PREFIX."liberty_structures` where `parent_id`=? order by `pos` desc"; + $result = $this->mDb->query($query,array((int)$previous["structure_id"])); + if ($res = $result->fetchRow()) { + $pos = $res["pos"]; + } else{ + $pos = 0; + } + $query = "update `".BIT_DB_PREFIX."liberty_structures` set `parent_id`=?, `pos`=(? + 1) where `structure_id`=?"; + $this->mDb->query( $query, array((int)$previous["structure_id"], (int)$pos, (int)$this->mStructureId) ); + //Move nodes up below that had previous parent and pos + $query = "update `".BIT_DB_PREFIX."liberty_structures` set `pos`=`pos`-1 where `pos`>? and `parent_id`=?"; + $this->mDb->query( $query, array( $this->mInfo['pos'], $this->mInfo['parent_id'] ) ); + } + $this->CompleteTrans(); + } + } + + /** + * moveNodeSouth + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function moveNodeSouth() { + if( $this->isValid() ) { + $this->StartTrans(); + $query = "select `structure_id`, `pos` from `".BIT_DB_PREFIX."liberty_structures` where `pos`>? and `parent_id`=? order by `pos` asc"; + $result = $this->mDb->query($query,array((int)$this->mInfo["pos"], (int)$this->mInfo["parent_id"])); + $res = $result->fetchRow(); + if ($res) { + //Swap position values + $query = "update `".BIT_DB_PREFIX."liberty_structures` set `pos`=? where `structure_id`=?"; + $this->mDb->query($query,array((int)$this->mInfo["pos"], (int)$res["structure_id"]) ); + $this->mDb->query($query,array((int)$res["pos"], (int)$this->mInfo["structure_id"]) ); + } + $this->CompleteTrans(); + } + } + + /** + * moveNodeNorth + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function moveNodeNorth() { + if( $this->isValid() ) { + $this->StartTrans(); + $query = "select `structure_id`, `pos` from `".BIT_DB_PREFIX."liberty_structures` where `pos`<? and `parent_id`=? order by `pos` desc"; + $result = $this->mDb->query($query,array((int)$this->mInfo["pos"], (int)$this->mInfo["parent_id"])); + $res = $result->fetchRow(); + if ($res) { + //Swap position values + $query = "update `".BIT_DB_PREFIX."liberty_structures` set `pos`=? where `structure_id`=?"; + $this->mDb->query($query,array((int)$res["pos"], (int)$this->mInfo["structure_id"]) ); + $this->mDb->query($query,array((int)$this->mInfo["pos"], (int)$res["structure_id"]) ); + } + $this->CompleteTrans(); + } + } + + + + + + + + + + // ============== OLD struct_lib STUFF + + + + function removeStructureNode( $structure_id, $delete=FALSE ) { + // Now recursively remove + if( @$this->verifyId( $structure_id ) ) { + $query = "SELECT * + FROM `".BIT_DB_PREFIX."liberty_structures` + WHERE `parent_id`=?"; + $result = $this->mDb->query( $query, array( (int)$structure_id ) ); + // Iterate down through the child nodes + while( $res = $result->fetchRow() ) { + $this->removeStructureNode( $res["structure_id"], $delete ); + } + + // Only delete a page if other structures arent referencing it + if( $delete ) { + $page_info = $this->getNode( $structure_id ); + $query = "SELECT COUNT(*) FROM `".BIT_DB_PREFIX."liberty_structures` WHERE `content_id`=?"; + $count = $this->mDb->getOne( $query, array( (int)$page_info["page_id"] ) ); + if( $count = 1 ) { + $this->remove_all_versions( $page_info["page_id"] ); + } + } + + // If we are removing the root node, remove the entry in liberty_content as well + $query = "SELECT `content_id` + FROM `".BIT_DB_PREFIX."liberty_structures` + WHERE `structure_id`=? AND `structure_id`=`root_structure_id`"; + $content_id = $this->mDb->getOne( $query, array( (int)$structure_id ) ); + + // Delete the liberty_content stuff + $lc = new LibertyContent($content_id); + $lc->expunge(); + + // Remove the structure node + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_structures` WHERE `structure_id`=?"; + $result = $this->mDb->query( $query, array( (int)$structure_id) ); + return true; + } + } + + /** + * Returns an array of info about the parent + * + * @param array $structure_id + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getStructureParentInfo($structure_id) { + $parent_id = $this->mDb->getOne( "SELECT `parent_id` FROM `".BIT_DB_PREFIX."liberty_structures` WHERE `structure_id`=?", array( (int)$structure_id ) ); + + if( !@BitBase::verifyId( $parent_id ) ) { + return null; + } + + return( $this->getNode( $parent_id ) ); + } + + /** + * getContentIds + * + * @param array $pStructureId + * @param array $pToc + * @param float $pLevel + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getContentIds( $pStructureId, &$pToc, $pLevel=0 ) { + $ret = array(); + + $query = "SELECT * from `".BIT_DB_PREFIX."liberty_structures` where `parent_id`=? ORDER BY pos, page_alias, content_id"; + $result = $this->mDb->query( $query, array( (int)$pStructureId ) ); + while ( $row = $result->fetchRow() ) { + array_push( $pToc, $row['content_id'] ); + $this->getContentIds( $row['structure_id'], $pToc, ++$pLevel ); + } + } + + /** + * getContentArray + * + * @param array $pStructureId + * @param array $pToc + * @param float $pLevel + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getContentArray( $pStructureId, &$pToc, $pLevel=0 ) { + $query = "SELECT * from `".BIT_DB_PREFIX."liberty_structures` where `structure_id`=?"; + $result = $this->mDb->query( $query, array( (int)$pStructureId ) ); + while ( $row = $result->fetchRow() ) { + array_push( $pToc, $row['content_id'] ); + $this->getContentIds( $pStructureId, $pToc, $pLevel ); + } + } + + /** + * exportHtml + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function exportHtml() { + $ret = array(); + $toc = array(); + $this->getContentArray( $this->mStructureId, $toc ); + if( count( $toc ) ) { + foreach( $toc as $conId ) { + if( $viewContent = LibertyBase::getLibertyObject( $conId ) ) { + $ret[] = array( + 'type' => $viewContent->mContentTypeGuid, + 'landscape' => FALSE, + 'url' => $viewContent->getDisplayUrl(), + 'content_id' => $viewContent->mContentId, + ); + } + } + } + return $ret; + } + + function isInStructure( $pContentId ) { + $ret = FALSE; + if( $this->isValid() ) { + $ret = $this->mDb->getOne( "SELECT structure_id FROM `".BIT_DB_PREFIX."liberty_structures` WHERE `root_structure_id`=? AND `content_id`=?", array( $this->mStructureId, $pContentId ) ); + } + return $ret; + } + + function loadStructure() { + if( $this->isValid() ) { + if( empty( $this->mTree ) ) { + $this->mTree = $this->buildSubtreeToc(); + } + } + return( !empty( $this->mTree ) ); + } + + /** + * buildSubtreeToc + * + * @param array $id + * @param array $slide + * @param string $order + * @param string $tocPrefix can be used to Prefix a subtree as it would start from a given number (e.g. 2.1.3) + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function buildTreeToc( $id, $order='asc', $tocPrefix='', $pPrefixDepth=1, $pDepth=1 ) { + if( $ret[0] = $this->getNode( $id ) ) { + $ret[0]['sub'] = $this->buildSubtreeToc( $id, $order, $tocPrefix, $pPrefixDepth, $pDepth ); + } + return $ret; + } + + + /** + * buildSubtreeToc + * + * @param array $id + * @param array $slide + * @param string $order + * @param string $tocPrefix can be used to Prefix a subtree as it would start from a given number (e.g. 2.1.3) + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function buildSubtreeToc( $id, $order='asc', $tocPrefix='', $pPrefixDepth=1, $pDepth=1 ) { + global $gLibertySystem, $gBitSystem; + $back = array(); + $cant = $this->mDb->getOne("select count(*) from `".BIT_DB_PREFIX."liberty_structures` where `parent_id`=?",array((int)$id)); + if ($cant) { + $query = "SELECT `structure_id`, `root_structure_id`, `parent_id`, `page_alias`, `pos`, `structure_level`, lc.`user_id`, lc.`title`, lc.`content_type_guid`, uu.`login`, uu.`real_name`, lc.`content_id`, lc.`last_modified`, lct.* + FROM `".BIT_DB_PREFIX."liberty_structures` ls + INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON ( lc.`content_id`=ls.`content_id` ) + INNER JOIN `".BIT_DB_PREFIX."liberty_content_types` lct ON ( lc.`content_type_guid`=lct.`content_type_guid` ) + LEFT JOIN `".BIT_DB_PREFIX."users_users` uu ON ( uu.`user_id` = lc.`user_id` ) + WHERE `parent_id`=? + ORDER BY ".$this->mDb->convertSortmode("pos_".$order); + $result = $this->mDb->query($query,array((int)$id)); + $prefix = 1; + $contentTypes = $gLibertySystem->mContentTypes; + while ($res = $result->fetchRow()) { + $res['prefix']=''; + if( $pDepth >= $pPrefixDepth ) { + $res['prefix']=($tocPrefix=='')?'':"$tocPrefix."; + $res['prefix'].=$prefix; + } + $prefix++; + if( !empty( $contentTypes[$res['content_type_guid']] ) ) { + // quick alias for code readability + $type = &$contentTypes[$res['content_type_guid']]; + if( empty( $type['content_object'] ) && !empty( $gBitSystem->mPackages[$type['handler_package']] ) ) { + // create *one* object for each object *type* to call virtual methods. + $handlerFile = $gBitSystem->mPackages[$type['handler_package']]['path'].$type['handler_file']; + if( file_exists( $handlerFile ) ) { + include_once( $handlerFile ); + if( class_exists( $type['handler_class'] ) ) { + $type['content_object'] = new $type['handler_class'](); + } + } + } + if( !empty( $type['content_object'] ) && is_object( $type['content_object'] ) ) { + $res['title'] = $type['content_object']->getTitleFromHash( $res ); + } + if ($res['structure_id'] != $id) { + $sub = $this->buildSubtreeToc( $res['structure_id'],$order,$res['prefix'], $pPrefixDepth, ($pDepth + 1) ); + if (is_array($sub)) { + $res['sub'] = $sub; + } + } + } + $pkgPath = strtoupper( $res['handler_package'] ).'_PKG_PATH'; + if( defined( $pkgPath ) ) { + $classFile = constant( strtoupper( $res['handler_package'] ).'_PKG_PATH' ).$res['handler_file']; + if( file_exists( $classFile ) ) { + require_once( $classFile ); + if( class_exists( $res['handler_class'] ) ) { + $res['display_url'] = $res['handler_class']::getDisplayUrlFromHash( $res ); + } + } + } + $back[] = $res; + } + } else { + return false; + } + return $back; + } + + /** + * getToc + * + * @param array $pStructureId + * @param string $order + * @param array $showdesc + * @param array $numbering + * @param string $numberPrefix + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getToc($pStructureId=NULL,$order='asc',$showdesc=false,$pNumberDepth=true,$numberPrefix='',$pCss='') { + if( !@$this->verifyId( $pStructureId ) ) { + $pStructureId = $this->mStructureId; + } + $structureTree = $this->buildSubtreeToc( $pStructureId, $order, $numberPrefix, $pNumberDepth ); + return '<div class="aciTree" id="structure-'.$this->mStructureId.'">'.$this->fetchToc( $structureTree, $showdesc, $pNumberDepth, $pCss ).'</div>'; + } + + /** + * fetchToc + * + * @param array $structureTree + * @param array $showdesc + * @param array $numbering + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function fetchToc($structureTree,$showdesc,$numbering,$pCss='') { + global $gBitSmarty; + $ret=''; + if ($structureTree != '') { + $gBitSmarty->verifyCompileDir(); + $gBitSmarty->assignByRef( 'structureId', $this->mStructureId ); + $ret.=$gBitSmarty->fetch( "bitpackage:liberty/structure_toc_startul.tpl"); + foreach($structureTree as $leaf) { + //echo "<br />";print_r($leaf);echo "<br />"; + $gBitSmarty->assignByRef('structure_tree',$leaf); + $gBitSmarty->assign('showdesc',$showdesc); + $gBitSmarty->assign('numbering',$numbering); + $ret .= $gBitSmarty->fetch( "bitpackage:liberty/structure_toc_leaf.tpl"); + if(isset($leaf["sub"]) && is_array($leaf["sub"])) { + // recurse down in - li tags are for w3c standard + $ret .= $this->fetchToc($leaf["sub"],$showdesc,$numbering); + } + $ret .= '</li>'; + } + $ret.=$gBitSmarty->fetch( "bitpackage:liberty/structure_toc_endul.tpl"); + } + return $ret; + } + + /** + * getNextStructureNode + * + * @param array $structure_id + * @param array $deep + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getNextStructureNode($structure_id, $deep = true) { + // If we have children then get the first child + if ($deep) { + $query = "SELECT `structure_id` + FROM `".BIT_DB_PREFIX."liberty_structures` ls + WHERE `parent_id`=? + ORDER BY ".$this->mDb->convertSortmode("pos_asc"); + $result1 = $this->mDb->query($query,array((int)$structure_id)); + + if ($result1->numRows()) { + $res = $result1->fetchRow(); + return $res["structure_id"]; + } + } + + // Try to get the next page with the same parent as this + $page_info = $this->getNode($structure_id); + $parent_id = $page_info["parent_id"]; + $page_pos = $page_info["pos"]; + + if (!$parent_id) + return null; + + $query = "SELECT `structure_id` + FROM `".BIT_DB_PREFIX."liberty_structures` ls + WHERE `parent_id`=? and `pos`>? + ORDER BY ".$this->mDb->convertSortmode("pos_asc"); + $result2 = $this->mDb->query($query,array((int)$parent_id, (int)$page_pos)); + + if ($result2->numRows()) { + $res = $result2->fetchRow(); + return $res["structure_id"]; + } + else { + return $this->getNextStructureNode($parent_id, false); + } + } + + /** + * getPrevStructureNode + * + * @param array $structure_id + * @param array $deep + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getPrevStructureNode($structure_id, $deep = false) { + //Drill down to last child for this tree node + if ($deep) { + $query = "select `structure_id` "; + $query .= "from `".BIT_DB_PREFIX."liberty_structures` ls "; + $query .= "where `parent_id`=? "; + $query .= "order by ".$this->mDb->convertSortmode("pos_desc"); + $result = $this->mDb->query($query,array($structure_id)); + + if ($result->numRows()) { + //There are more children + $res = $result->fetchRow(); + $structure_id = $this->getPrevStructureNode($res["structure_id"], true); + } + return $structure_id; + } + // Try to get the previous page with the same parent as this + $page_info = $this->getNode($structure_id); + $parent_id = $page_info["parent_id"]; + $pos = $page_info["pos"]; + + //At the top of the tree + if (!isset($parent_id)) + return null; + + $query = "select `structure_id` "; + $query .= "from `".BIT_DB_PREFIX."liberty_structures` ls "; + $query .= "where `parent_id`=? and `pos`<? "; + $query .= "order by ".$this->mDb->convertSortmode("pos_desc"); + $result = $this->mDb->query($query,array((int)$parent_id, (int)$pos)); + + if ($result->numRows()) { + //There is a previous sibling + $res = $result->fetchRow(); + $structure_id = $this->getPrevStructureNode($res["structure_id"], true); + } + else { + //No previous siblings, just the parent + $structure_id = $parent_id; + } + return $structure_id; + } + + /** + * Return an array of subpages + * + * @param array $pParentId + * @access public + * @return array of child structure pages + */ + function getStructureNodes( $pParentId ) { + $ret = array(); + $query = "SELECT `pos`, `structure_id`, `parent_id`, ls.`content_id`, lc.`title`, `page_alias` + FROM `".BIT_DB_PREFIX."liberty_structures` ls, `".BIT_DB_PREFIX."liberty_content` lc + WHERE ls.`content_id` = lc.`content_id` AND `parent_id`=? "; + $query .= "order by ".$this->mDb->convertSortmode("pos_asc"); + $result = $this->mDb->query($query,array((int)$pParentId)); + while ($res = $result->fetchRow()) { + //$ret[] = $this->populate_page_info($res); + $ret[] = $res; + } + return $ret; + } +} +?> diff --git a/includes/classes/LibertySystem.php b/includes/classes/LibertySystem.php new file mode 100644 index 0000000..9165fc2 --- /dev/null +++ b/includes/classes/LibertySystem.php @@ -0,0 +1,865 @@ +<?php +/** +* System class for handling the liberty package +* +* @package liberty +* @version $Header$ +* @author spider <spider@steelsun.com> +*/ + +// +----------------------------------------------------------------------+ +// | Copyright (c) 2004, bitweaver.org +// +----------------------------------------------------------------------+ +// | 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 +// | +// | For comments, please use phpdocu.sourceforge.net documentation standards!!! +// | -> see http://phpdocu.sourceforge.net/ +// +----------------------------------------------------------------------+ +// | Authors: spider <spider@steelsun.com> +// +----------------------------------------------------------------------+ + +/** + * Local base defines + */ +// Plugin Definitions +define( 'STORAGE_PLUGIN', 'storage' ); +define( 'FORMAT_PLUGIN', 'format' ); +define( 'DATA_PLUGIN', 'data' ); +define( 'MIME_PLUGIN', 'mime' ); +define( 'FILTER_PLUGIN', 'filter' ); + +if( !defined( 'LIBERTY_DEFAULT_MIME_HANDLER' )) { + define( 'LIBERTY_DEFAULT_MIME_HANDLER', 'mimedefault' ); +} + +// Service Definitions +define( 'LIBERTY_SERVICE_ACCESS_CONTROL', 'access_control' ); +define( 'LIBERTY_SERVICE_CATEGORIZATION', 'categorization' ); +define( 'LIBERTY_SERVICE_COMMERCE', 'commerce' ); +define( 'LIBERTY_SERVICE_CONTENT_TEMPLATES', 'content_templates' ); +define( 'LIBERTY_SERVICE_DOCUMENT_GENERATION', 'document_generation' ); +define( 'LIBERTY_SERVICE_FORUMS', 'forums' ); +define( 'LIBERTY_SERVICE_GROUP', 'groups' ); +define( 'LIBERTY_SERVICE_MAPS', 'map_display' ); +define( 'LIBERTY_SERVICE_METADATA', 'metadata' ); +define( 'LIBERTY_SERVICE_MENU', 'menu' ); +define( 'LIBERTY_SERVICE_RATING', 'rating' ); +define( 'LIBERTY_SERVICE_REBLOG', 'reblogging_rss_feeds' ); +define( 'LIBERTY_SERVICE_SEARCH', 'search' ); +define( 'LIBERTY_SERVICE_THEMES', 'themes' ); +define( 'LIBERTY_SERVICE_TOPICA', 'topica' ); +define( 'LIBERTY_SERVICE_TRANSLATION', 'translation' ); +define( 'LIBERTY_SERVICE_TRANSLITERATION', 'transliteration' ); +define( 'LIBERTY_SERVICE_LIBERTYSECURE', 'security' ); +define( 'LIBERTY_SERVICE_MODCOMMENTS', 'comment_moderation' ); +define( 'LIBERTY_SERVICE_UPLOAD', 'upload' ); + +define( 'LIBERTY_TEXT_AREA', 'editliberty' ); +define( 'LIBERTY_UPLOAD', 'upload' ); + +/** + * Link to base class + */ +require_once( LIBERTY_PKG_CLASS_PATH.'LibertyBase.php' ); + +/** + * System class for handling the liberty package + * + * @package liberty + */ +class LibertySystem extends BitSingleton { + + + // Hash of plugin data + public $mPlugins = array(); + + // Liberty data tags + public $mDataTags = array(); + + // Content Status + public $mContentStatus; + + // Content types + public $mContentTypes; + + // File name of last plug that registered + public $mPluginFileName; + + // Packages using LibertySystem + // this makes it possible to extend LibertySystem by another package + public $mSystem = LIBERTY_PKG_NAME; + public $mPluginPath; + + + /** + * Initiate Class + **/ + function __construct( $pExtras = TRUE ) { + parent::__construct(); + + $this->mPluginPath = LIBERTY_PKG_PATH.'plugins/'; + + $this->loadContentTypes(); + } + + + public function __sleep() { + return array_merge( parent::__sleep(), array( 'mPlugins', 'mDataTags', 'mContentStatus', 'mContentTypes', 'mPluginFileName', 'mSystem', 'mPluginPath' ) ); + } + + // ****************************** Plugin Functions + /** + * Load only active plugins from disk + * + * @return none + * @access public + **/ + function loadActivePlugins() { + global $gBitSystem; + // all active plugins + $configs = array_keys( $gBitSystem->getConfigMatch( "/^{$this->mSystem}_plugin_status_/i", 'y' )); + + // first we include the default one - this allows other plugins to make use of default functions + if( $this->mSystem == LIBERTY_PKG_NAME ) { + if( $key = array_search( 'liberty_plugin_status_'.LIBERTY_DEFAULT_MIME_HANDLER , $configs )) { + unset( $configs[$key] ); + } + array_unshift( $configs, 'liberty_plugin_status_'.LIBERTY_DEFAULT_MIME_HANDLER ); + } + + foreach( $configs as $config ) { + $pluginGuid = preg_replace( "/^{$this->mSystem}_plugin_status_/", '', $config, 1 ); + if( $pluginFile = $gBitSystem->getConfig( "{$this->mSystem}_plugin_path_$pluginGuid" ) ) { + if( is_file( BIT_ROOT_PATH.$pluginFile )) { + $this->mPluginFilePath = BIT_ROOT_PATH.$pluginFile; + include_once( BIT_ROOT_PATH.$pluginFile ); + } + } elseif( $pluginFile = $gBitSystem->getConfig( "{$this->mSystem}_plugin_file_$pluginGuid" ) ) { + // TODO: all this is deprecated and doesn't really rock bitweavers boat anymore - we use the _plugin_path_ setting now. + // this code here is only relevant if a user has updated bitweaver and scanAllPlugins() hasn't been called yet. + // scanAllPlugins() is called during the upgrade in the installer so we really are only keeping this here for CVS users + // and people who use nexus since it makes use of this plugin system as well. + // - xing - Saturday Jul 05, 2008 20:47:29 CEST + + // check for the plugin in the default location - in case bitweaver root path changed. + + if( file_exists( $pluginFile )) { + $this->mPluginFilePath = $pluginFile; + include_once( $pluginFile ); + } else { + $defaultFile = $this->mPluginPath.basename( $pluginFile ); + if( file_exists( $defaultFile )) { + $this->mPluginFilePath = $defaultFile; + include_once( $defaultFile ); + } + } + } + } + } + + /** + * Load all plugins found in specified directory + * Use loadActivePlugins to load only the active plugins + * + * @param string $pPluginsPath Set the path where to scan for plugins + * @param string $pPrefixPattern Perl regex for filenames can start with to prevent inclusion of unwanted filenames (e.g. (data\.|storage\.)). Final regex: /^{$pPrefixPattern}.*\.php$/ + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function scanAllPlugins( $pPluginsPath = NULL, $pPrefixPattern = NULL ) { + global $gBitSystem; + if( empty( $pPluginsPath )) { + $pPluginsPath = $this->mPluginPath; + } + + // check for plugins in plugins/ dir + if( $pluginHandle = opendir( $pPluginsPath )) { + while( FALSE !== ( $plugin = readdir( $pluginHandle ) ) ) { + $pattern = "/^{$pPrefixPattern}.*\.php$/"; + if( preg_match( $pattern, $plugin ) ) { + $this->mPluginFilePath = $pPluginsPath.$plugin; + include_once( $pPluginsPath.$plugin ); + } + } + } + + // check for liberty plugins in other packages as well + if( $this->mSystem == LIBERTY_PKG_NAME && $pkgHandle = opendir( BIT_ROOT_PATH )) { + while( FALSE !== ( $dirName = readdir( $pkgHandle ))) { + if( preg_match( '/^\w/', $dirName ) && $dirName != 'CVS' && is_dir( $pluginDir = BIT_ROOT_PATH.$dirName.'/liberty_plugins/' ) && ( $pluginHandle = opendir( $pluginDir ))) { + while( FALSE !== ( $plugin = readdir( $pluginHandle ))) { + if( preg_match( "/^{$pPrefixPattern}.*\.php$/", $plugin )) { + $this->mPluginFilePath = $pluginDir.$plugin; + include_once( $pluginDir.$plugin ); + } + } + } + } + } + + // keep plugin list in sorted order + if( !empty( $this->mPlugins ) and is_array( $this->mPlugins ) ) { + asort( $this->mPlugins ); + } + + // only execute the following if this class hasn't been extended + if( $this->mSystem == LIBERTY_PKG_NAME ) { + // There must be at least one format plugin active and set as the default format + $format_plugin_count = $default_format_found = 0; + $current_default_format_guid = $gBitSystem->getConfig( 'default_format' ); + foreach( $this->mPlugins as $guid => $plugin ) { + // load all the requirements that we can display them on the plugin page + if( $requirement_func = $this->getPluginFunction( $guid, 'requirement_function', FALSE, TRUE )) { + $this->mPlugins[$guid]['requirements'] = $requirement_func(); + } + + if( $this->isPluginActive( $guid )) { + if( $plugin['plugin_type'] == FORMAT_PLUGIN ) { + $format_plugin_count++; + } + if( $current_default_format_guid == $guid ) { + $default_format_found++; + } + } + } + + // if no current default format or no format plugins active + // activate format.tikiwiki and make it the default format plugin + // This happens during installation and therefore requires that we include the plugin file for the constant definitions + $plugin_file = $this->mPluginPath.'format.tikiwiki.php'; + if( $format_plugin_count == 0 || $default_format_found == 0 && is_file( $plugin_file ) ) { + require_once( $plugin_file ); + $this->setActivePlugin( PLUGIN_GUID_TIKIWIKI ); + $gBitSystem->storeConfig( 'default_format', PLUGIN_GUID_TIKIWIKI, $this->mSystem ); + //make memory match db + $this->loadActivePlugins(); + } + } + + // remove any config settings for plugin files that have been removed + $plugins = $gBitSystem->getConfigMatch( "/^{$this->mSystem}_plugin_path_/" ); + foreach( $plugins as $config => $path ) { + if( !is_file( BIT_ROOT_PATH.$path )) { + $guid = str_replace( "{$this->mSystem}_plugin_path_", '', $config ); + $gBitSystem->storeConfigMatch( "/^{$this->mSystem}_plugin_\w+_$guid/i", NULL ); + } + } + + // TODO: we can remove this at some point since it's not really important - it just clears out stuff from the database that we don't use anymore + $gBitSystem->storeConfigMatch( "/^{$this->mSystem}_plugin_file_/", NULL ); + } + + /** + * Check to see if a given plugin is activ or not + * + * @param $pPluginGuid Plugin GUID of the plugin you want to check + * @return TRUE if the plugin is active, FALSE if it's not + **/ + function isPluginActive( $pPluginGuid ) { + return( !empty( $this->mPlugins[$pPluginGuid]['is_active'] ) && ( $this->mPlugins[$pPluginGuid]['is_active'] == 'y' )); + } + + /** + * Allow data plugins to register their tag + * + * @param string $pTag Tag of plugin, e.g.: TOC + * @param string $pPluginGuid GUID of plugin, e.g.: PLUGIN_GUID_TOC + * @access public + * @return void + */ + function registerDataTag( $pTag, $pPluginGuid ) { + $this->mDataTags[strtolower( $pTag )] = $pPluginGuid; + } + + /** + * Allow plugins to register themselves using this function. Data is added directly to the list of existing plugins + * + * @param $pGuid GUID of plugin + * @param $pPluginParams Set of plugin parameters (see treasury/plugins/mime.*.php for example) + * @return none + * @access public + **/ + function registerPlugin( $pGuid, $pPluginParams ) { + global $gBitSystem; + // plugins can set their own file_name. this is not mandatory but makes sure we store the path to the correct file + // this is useful for files that are included by other plugins + if( !empty( $pPluginParams['file_name'] )) { + $pluginPath = dirname( $this->mPluginFilePath )."/".$pPluginParams['file_name']; + } else { + $pluginPath = $this->mPluginFilePath; + } + + if( !empty( $pGuid ) && !empty( $pluginPath ) && is_file( $pluginPath ) ) { + // store the relative path - we need to store the path to all plugins and not just active ones since we don't have access to this information when we use setActivePlugins() + $gBitSystem->storeConfig( "{$this->mSystem}_plugin_path_".$pGuid, str_replace( BIT_ROOT_PATH, "", $pluginPath ), LIBERTY_PKG_NAME ); + $settings['is_active'] = $gBitSystem->getConfig( "{$this->mSystem}_plugin_status_".$pGuid ); + if( empty( $settings['is_active'] ) && !empty( $pPluginParams['auto_activate'] )) { + $this->setActivePlugin( $pGuid ); + } + $settings['plugin_guid'] = $pGuid; + $this->mPlugins[$pGuid] = array_merge( $settings, $pPluginParams ); + } + + } + + /** + * setActivePlugins + * + * @param array $pPluginGuids an array of all the plugin guids that are active. Any left out are *inactive*! + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function setActivePlugins( $pPluginGuids ) { + global $gBitSystem; + + if( is_array( $pPluginGuids ) ) { + // zap list of plugins from DB + $gBitSystem->storeConfigMatch( "/^{$this->mSystem}_plugin_status/i", NULL, 'n', LIBERTY_PKG_NAME ); + foreach( array_keys( $this->mPlugins ) as $guid ) { + $this->mPlugins[$guid]['is_active'] = 'n'; + } + + // set active those specified + foreach( array_keys( $pPluginGuids ) as $guid ) { + if( $pPluginGuids[$guid][0] == 'y' ) { + $this->setActivePlugin( $guid ); + } + } + // load any plugins made active, but not already loaded + $this->loadActivePlugins(); + + // finally we need to remove all cache files since the content has been changed + LibertyContent::expungeCache(); + } + } + + /** + * set a single plugin as active and store the appropriate information in the database + * + * @param array $pPluginGuid the plugin guid we want to set active + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function setActivePlugin( $pPluginGuid ) { + global $gBitSystem; + $gBitSystem->storeConfig( "{$this->mSystem}_plugin_status_".$pPluginGuid, 'y', LIBERTY_PKG_NAME ); + if( isset( $this->mPlugins[$pPluginGuid] )) { + $this->mPlugins[$pPluginGuid]['is_active'] = 'y'; + } + + // the requirement function can return a set of tables, indexes and sequences that need to be created for the plugin to work. + if( $requirement_func = $this->getPluginFunction( $pPluginGuid, 'requirement_function' )) { + $reqs = $requirement_func( TRUE ); + if( !empty( $reqs['schema']['tables'] )) { + // fetch a list of tables in the database that we know if we need to insert any plugin ones + if( strlen( BIT_DB_PREFIX ) > 0 ) { + $lastQuote = strrpos( BIT_DB_PREFIX, '`' ); + if( $lastQuote != FALSE ) { + $lastQuote++; + } + $prefix = substr( BIT_DB_PREFIX, $lastQuote ); + } else { + $prefix = ''; + } + + global $gBitDbType, $gBitDbHost, $gBitDbUser, $gBitDbPassword, $gBitDbName; + $db = &ADONewConnection( $gBitDbType ); + if( $db->Connect( $gBitDbHost, $gBitDbUser, $gBitDbPassword, $gBitDbName )) { + $dict = NewDataDictionary( $db ); + + if( !$gBitSystem->mDb->getCaseSensitivity() ) { + $dict->connection->nameQuote = ''; + } + + if( $dbTables = $gBitSystem->mDb->MetaTables( 'TABLES', FALSE, ( $prefix ? $prefix.'%' : NULL ))) { + // If we use MySql check which storage engine to use + if( isset( $_SESSION['use_innodb'] )) { + if( $_SESSION['use_innodb'] == TRUE ) { + $build = array( 'NEW', 'MYSQL' => 'ENGINE=INNODB' ); + } else { + $build = array( 'NEW', 'MYSQL' => 'ENGINE=MYISAM' ); + } + } else { + $build = 'NEW'; + } + + // create tables + foreach( $reqs['schema']['tables'] as $table => $tableDict ) { + $fullTable = $prefix.$table; + if( !in_array( $fullTable, $dbTables )) { + if( $sql = $dict->CreateTableSQL( $fullTable, $tableDict, $build )) { + $ret = $dict->ExecuteSQLArray( $sql ); + if( $ret === FALSE ) { + $errors[] = 'Failed to create table '.$completeTableName; + $tablesInstalled = TRUE; + } + } + } + } + + // only continue if we installed at least one table + if( !empty( $tablesInstalled )) { + $schemaQuote = strrpos( BIT_DB_PREFIX, '`' ); + $sequencePrefix = ( $schemaQuote ? substr( BIT_DB_PREFIX, $schemaQuote + 1 ) : BIT_DB_PREFIX ); + + // create indexes + if( !empty( $reqs['schema']['indexes'] )) { + foreach( $reqs['schema']['indexes'] as $idx => $idxDict ) { + $completeTableName = $sequencePrefix.$reqs['schema']['indexes'][$idx]['table']; + if( $sql = $dict->CreateIndexSQL( $idx, $completeTableName, $reqs['schema']['indexes'][$idx]['cols'], $reqs['schema']['indexes'][$idx]['opts'] )) { + $ret = $dict->ExecuteSQLArray( $sql ); + if( $ret === FALSE ) { + $errors[] = 'Failed to create index '.$completeTableName; + } + } + } + } + + // create sequences + if( !empty( $reqs['schema']['sequences'] )) { + // If we use InnoDB for MySql we need this to get sequence tables created correctly. + if( isset( $_SESSION['use_innodb'] ) ) { + if( $_SESSION['use_innodb'] == TRUE ) { + $gBitInstallDb->_genSeqSQL = "create table %s (id int not null) ENGINE=INNODB"; + } else { + $gBitInstallDb->_genSeqSQL = "create table %s (id int not null) ENGINE=MYISAM"; + } + } + + foreach( array_keys( $reqs['schema']['sequences'] ) as $sequenceIdx ) { + if( !$gBitInstallDb->CreateSequence( $sequencePrefix.$sequenceIdx, $reqs['schema']['sequences'][$sequenceIdx]['start'] )) { + $errors[] = 'Failed to create sequence '.$sequencePrefix.$sequenceIdx; + } + } + } + } + } + } + } + } + + return( !empty( $errors ) ? $errors : NULL ); + } + + /** + * getPluginInfo + * + * @param array $pGuid + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function getPluginInfo( $pGuid ) { + $ret = NULL; + if( !empty( $pGuid ) && !empty( $this->mPlugins[$pGuid] )) { + $ret = $this->mPlugins[$pGuid]; + } + return $ret; + } + + /** + * getPluginFunction + * + * @param string $pGuid GUID of plugin used - if empty, we get all available functions of that type in all active plugins + * @param string $pFunctionName Function type we want to use + * @param string $pGetDefault Get default function for a given plugin type such as 'mime' + * @param string $pGetInactive don't worry if plugin is active or not + * @access public + * @return function name on success, NULL on failure + */ + function getPluginFunction( $pGuid, $pFunctionName, $pGetDefault = FALSE, $pGetInactive = FALSE ) { + if(( $this->isPluginActive( $pGuid ) || $pGetInactive ) && !empty( $this->mPlugins[$pGuid][$pFunctionName] ) && function_exists( $this->mPlugins[$pGuid][$pFunctionName] )) { + $ret = $this->mPlugins[$pGuid][$pFunctionName]; + } + + // if we can't get a function on the first round, we fetch the default + if( empty( $ret ) && $pGetDefault == 'mime' && $pGuid != LIBERTY_DEFAULT_MIME_HANDLER ) { + $ret = $this->getPluginFunction( LIBERTY_DEFAULT_MIME_HANDLER, $pFunctionName ); + } + + return( !empty( $ret ) ? $ret : NULL ); + } + + /** + * getPluginFunctions Get a list of functions of a given type + * + * @param string $pFunctionName Function type we want to get + * @access public + * @return array of functions with the GUID as key + */ + function getPluginFunctions( $pFunctionName ) { + foreach( $this->mPlugins as $guid => $plugin ) { + if( $this->isPluginActive( $guid ) && !empty( $plugin[$pFunctionName] ) && function_exists( $plugin[$pFunctionName] )) { + $ret[$guid] = $plugin[$pFunctionName]; + } + } + + return( !empty( $ret ) ? $ret : array() ); + } + + /** + * getMimeTemplate will fetch an appropriate template to display a given filetype + * + * @param string $pTemplate Basename of the template + * @param string $pGuid GUID of plugin + * @access public + * @return resource path to template + */ + function getMimeTemplate( $pTemplate, $pGuid = LIBERTY_DEFAULT_MIME_HANDLER ) { + $ret = NULL; + if( $this->isPluginActive( $pGuid ) && ( $plugin = $this->getPluginInfo( $pGuid )) && !empty( $plugin[$pTemplate.'_tpl'] )) { + $ret = $plugin[$pTemplate.'_tpl']; + } elseif( $pGuid != LIBERTY_DEFAULT_MIME_HANDLER ) { + $ret = $this->getMimeTemplate( $pTemplate ); + } + return $ret; + } + + /** + * getAllMimeTemplates will fetch templates of a given type from all active plugins + * + * @param array $pTemplate Name of the template + * @access public + * @return array of resource paths to templates + */ + function getAllMimeTemplates( $pTemplate ) { + $ret = array(); + foreach( $this->getPluginsOfType( MIME_PLUGIN ) as $guid => $plugin ) { + if( $this->isPluginActive( $guid ) && !empty( $plugin[$pTemplate.'_tpl'] )) { + $ret[] = $plugin[$pTemplate.'_tpl']; + } + } + return $ret; + } + + /** + * getPluginsOfType will fetch all plugins of a given type + * + * @param string $pPluginType + * @access public + * @return an array of plugins of a given type + */ + function getPluginsOfType( $pPluginType ) { + $ret = array(); + if( !empty( $pPluginType )) { + foreach( $this->mPlugins as $guid => $plugin ) { + if( !empty( $plugin['plugin_type'] ) && $plugin['plugin_type'] == $pPluginType ) { + $ret[$guid] = $plugin; + } + } + } + return $ret; + } + + /** + * This function will purge all plugin settings set in kernel_config. useful when the path to plugins changes + * or plugins don't seem to be working + * + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ + function resetAllPluginSettings() { + global $gBitSystem; + $gBitSystem->storeConfigMatch( "/^{$this->mSystem}_plugin_/", NULL ); + if( $this->mSystem == LIBERTY_PKG_NAME ) { + // also remove the default format + $gBitSystem->storeConfig( 'default_format', NULL, $this->mSystem ); + } + $this->scanAllPlugins(); + } + + /** + * Load all available content types into $this->mContentTypes + * + * @return none + **/ + function loadContentTypes( $pCacheTime=BIT_QUERY_CACHE_TIME ) { + if( $rs = $this->mDb->query( "SELECT * FROM `".BIT_DB_PREFIX."liberty_content_types`", FALSE, BIT_QUERY_DEFAULT, BIT_QUERY_DEFAULT ) ) { + while( $row = $rs->fetchRow() ) { + // translate name + // content_description backward compatibility for now + $row['content_description'] = $row['content_name'] = tra( $row['content_name'] ); + if( !empty( $row['content_name_plural'] ) ){ + $row['content_name_plural'] = tra( $row['content_name_plural'] ); + } + $this->mContentTypes[$row['content_type_guid']] = $row; + } + } + } + + /** + * Register new content type + * + * @return none + * @access public + **/ + function registerContentType( $pGuid, $pTypeParams ) { + global $gBitSystem; + if ( !$this->mDb->isValid() ) return; + if( !isset( $this->mContentTypes ) ) { + $this->loadContentTypes(); + } + $pTypeParams['content_type_guid'] = $pGuid; + // automagically populate plural name value if none is set using most comment english of appending 's' + if( empty( $pTypeParams['content_name_plural'] ) ){ + $pTypeParams['content_name_plural'] = $pTypeParams['content_name'].'s'; + } + if( empty( $this->mContentTypes[$pGuid] ) && !empty( $pTypeParams ) ) { + $this->StartTrans(); + $result = $this->mDb->associateInsert( BIT_DB_PREFIX."liberty_content_types", $pTypeParams ); + $this->CompleteTrans(); + // we just ran some SQL - let's flush the loadContentTypes query cache + $this->loadContentTypes( 0 ); + } else { + if( $pTypeParams['handler_package'] != $this->mContentTypes[$pGuid]['handler_package'] || + $pTypeParams['handler_file'] != $this->mContentTypes[$pGuid]['handler_file'] || + $pTypeParams['handler_class'] != $this->mContentTypes[$pGuid]['handler_class'] || + ( empty( $this->mContentTypes[$pGuid]['content_name_plural'] ) && version_compare( $gBitSystem->getVersion( LIBERTY_PKG_NAME ), '2.1.4', '>=' ) ) // temporary update condition during migration of content_description to content_name remove after april 20 2011 + ) { + $this->StartTrans(); + $result = $this->mDb->associateUpdate( BIT_DB_PREFIX."liberty_content_types", $pTypeParams, array( 'content_type_guid'=>$pGuid ) ); + $this->CompleteTrans(); + // we just ran some SQL - let's flush the loadContentTypes query cache + $this->loadContentTypes( 0 ); + } + } + } + + /** + * requireHandlerFile will require_once() the handler file if given the hash found in $gLibertySystem->mContentTypes[content_type_guid] + * + * @param array $pContentTypeHash the hash found in $gLibertySystem->mContentTypes[content_type_guid] + * @access public + * @return TRUE on success, FALSE on failure + */ + private function requireHandlerFile( $pContentTypeHash ) { + $ret = FALSE; + $pkgName = strtoupper( $pContentTypeHash['handler_package'] ); + foreach( array( '_PKG_CLASS_PATH', '_PKG_INCLUDE_PATH', '_PKG_PATH' ) as $pkgConstPath ) { + if( defined( $pkgName.$pkgConstPath ) && ($pkgDef = constant( $pkgName.$pkgConstPath )) ) { + $handlerFile = $pkgDef.$pContentTypeHash['handler_file']; + if( is_file( $handlerFile ) ) { + require_once( $handlerFile ); + $ret = TRUE; + } + } + } + return $ret; + } + + /** + * Get the display name of the content type + * @param boolean $pPlural true will return the plural form of the content type display name + * @return string the display name of the content type + */ + function getContentType( $pContentTypeGuid ){ + $ret = NULL; + if( !isset( $this->mContentTypes ) ) { + $this->loadContentTypes(); + } + if( !empty( $this->mContentTypes[$pContentTypeGuid] ) ) { + $ret = $this->mContentTypes[$pContentTypeGuid]; + } + return $ret; + } + + /** + * Get the display name of the content type + * @param boolean $pPlural true will return the plural form of the content type display name + * @return string the display name of the content type + */ + public function getContentClassName( $pContentTypeGuid ) { + $ret = NULL; + if( !isset( $this->mContentTypes ) ) { + $this->loadContentTypes(); + } + if( !empty( $this->mContentTypes[$pContentTypeGuid] ) && $this->requireHandlerFile( $this->mContentTypes[$pContentTypeGuid] ) ) { + $ret = $this->mContentTypes[$pContentTypeGuid]['handler_class']; + } + return $ret; + } + + /** + * Get the display name of the content type + * @param boolean $pPlural true will return the plural form of the content type display name + * @return string the display name of the content type + */ + function getContentTypeName( $pContentTypeGuid, $pPlural=FALSE ){ + $ret = NULL; + if( $pPlural && isset( $this->mContentTypes[$pContentTypeGuid]['content_name_plural'] ) ) { + $ret = tra( $this->mContentTypes[$pContentTypeGuid]['content_name_plural'] ); + } elseif( !empty( $this->mContentTypes[$pContentTypeGuid]['content_name'] ) ) { + $ret = tra( $this->mContentTypes[$pContentTypeGuid]['content_name'] ); + } + return $ret; + } + + /** + * Get the description of a given content type + * + * @param $pContentType Content type GUID you want the description for + * @return Content type description + * @access public + **/ + function getContentTypeDescription( $pContentType ) { + deprecated( 'You are calling the deprecated method getContentTypeDescription, use getContentTypeName( $pPlural )' ); + return $this->getContentTypeName( $pContentType ); + } + + + + + // ****************************** Service Functions + /** + * Get the service details of a given package + * + * @param $pPackageName Package name of you want the service details for + * @return Service details if the package has them - FALSE if the package is not a service + * @access public + **/ + function getService( $pPackageName ) { + global $gBitSystem; + return( !empty( $gBitSystem->mPackages[$pPackageName]['service'] ) ? $gBitSystem->mPackages[$pPackageName]['service'] : NULL ); + } + + /** + * Register package as service - hash added to $this->mServices + * + * $pServiceHash Service hash details. see existing service hashes found in <package>/bit_setup_inc.php for examples and details + * @return none + * @access public + **/ + function registerService( $pServiceName, $pPackageName, $pServiceHash, $pOptions = array() ) { + $this->mServices[$pServiceName] = array( + 'package' => $pPackageName, + 'services' => $pServiceHash, + 'description' => !empty( $pOptions['description'] ) ? $pOptions['description'] : NULL, + 'required' => !empty( $pOptions['required'] ) ? $pOptions['required'] : FALSE, + ); + } + + /** + * Check to see if a package has any service capabilities + * + * @return TRUE on success, FALSE on failure + * @access public + **/ + function hasService( $pServiceName ) { + return( !empty( $this->mServices[$pServiceName] ) ); + } + + /** + * Get contents of a given service value + * + * @param $pServiceValue Service value you want to work to get + * @return Value of a given service value + * @access private + **/ + function getServiceValues( $pServiceValue ) { + global $gBitSystem; + $ret = NULL; + if( !empty( $this->mServices ) ) { + foreach( array_keys( $this->mServices ) as $service ) { + if( $this->hasService( $service ) ) { + // DEPRECATED - this is mostly circular logic - getting the package name from itself to look itself up + // Service names are key values - regardless of package + // Accessing services directly by name infact allows multiple packages to provide the same kind of service + /* + if( !($package = $gBitSystem->getConfig( 'liberty_service_'.$service )) ) { + $package = key( $this->mServices[$service] ); + } + if( !empty( $this->mServices[$service][$package][$pServiceValue] ) ) { + $ret[$service] = $this->mServices[$service][$package][$pServiceValue]; + } + */ + if( !empty( $this->mServices[$service]['services'][$pServiceValue] ) ) { + $ret[$service] = $this->mServices[$service]['services'][$pServiceValue]; + } + } + } + } + return $ret; + } + + + + + // ****************************** Miscellaneous Functions + /** + * Get the URL to the icon for the mime type passed in. This should probably check for files of multiple image types instead of just jpg + * + * @param string $pMimeType Mime type of the file + * @param string $pExt Extension of the file - used to get backup mime icon + * @access public + * @return Full image HTML tag to mime icon + */ + public static function getMimeThumbnailURL($pMimeType, $pExt=NULL) { + $ret = NULL; + $parts = explode( '/',$pMimeType ); + if( count( $parts ) > 1 ) { + global $gBitSmarty; + $gBitSmarty->loadPlugin( 'smarty_function_biticon' ); + + $ext = strtolower( $parts[1] ); + $biticon = array( + 'ipackage' => 'liberty', + 'ipath' => 'mime/', + 'iname' => $ext, + 'iexplain' => $ext, + 'url' => 'only', + ); + + if( !$ret = smarty_function_biticon( $biticon ) ) { + $biticon['iname'] = strtolower( $pExt ); + if( !$ret = smarty_function_biticon( $biticon ) ) { + $biticon['iname'] = 'generic'; + $ret = smarty_function_biticon( $biticon ); + } + } + } + return $ret; + } + + /** + * Will return the plugin that is responsible for the given mime type + * + * @param string $pFileHash['mimetype'] (required if no tmp_name) Mime type of file that needs to be dealt with + * @param string $pFileHash['tmp_name'] (required if no mimetype) Full path to file that needs to be dealt with + * @access public + * @return handler plugin guid + * TODO: Currently this will return the first found handler - might want to have a sort order? + **/ + function lookupMimeHandler( &$pFileHash ) { + global $gBitSystem; + + if( empty( $this->mPlugins )) { + $this->scanAllPlugins( NULL, "mime\." ); + } + + // we will do our best to work out what this file is. + // both these methods use a different method for fetching the filetype + // this can be particularly important when fetching the mime-type of video files. + // ! Windows looses the file extension when creating the tmp file + // need a better way of handling this + if( !is_windows() ) { + $pFileHash['type'] = $gBitSystem->verifyMimeType( $pFileHash['tmp_name'] ); + } + + if( $pFileHash['type'] == 'application/binary' || $pFileHash['type'] == 'application/octet-stream' || $pFileHash['type'] == 'application/octetstream' ) { + $pFileHash['type'] = $gBitSystem->lookupMimeType( $pFileHash['name'] ); + } + + foreach( $this->getPluginsOfType( MIME_PLUGIN ) as $handler => $plugin ) { + if( $this->isPluginActive( $handler ) && !empty( $plugin['mimetypes'] ) && is_array( $plugin['mimetypes'] )) { + foreach( $plugin['mimetypes'] as $pattern ) { + if( preg_match( $pattern, $pFileHash['type'] )) { + return $handler; + } + } + } + } + + return LIBERTY_DEFAULT_MIME_HANDLER; + } +} +?> diff --git a/includes/comments_inc.php b/includes/comments_inc.php new file mode 100644 index 0000000..b891d80 --- /dev/null +++ b/includes/comments_inc.php @@ -0,0 +1,368 @@ +<?php +/** + * comment_inc + * + * @author spider <spider@steelsun.com> + * @version $Revision$ + * @package liberty + * @subpackage functions + */ + +// 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. + +// This file sets up the information needed to display +// the comments preferences, post-comment box and the +// list of comments. Finally it displays comments.tpl +// using this information + +// Setup URLS for the Comments next and prev buttons and use variables that +// cannot be aliased by normal Bit variables. +// Traverse each _REQUEST data adn put them in an array + +// this script may only be included - so its better to die if called directly. + + +/** + * Parameters that need to be set when calling this file + * @param numeric $commentsParentId The content id of the object where a new comment will be attached (required) + * @param array $commentsParentIds The list of content id of object the comments will be displayed - if not defined $commentsParentId (required if $commentsParentId is not set) + * @param string $comments_return_url The URL the user should be sent to after posting the comment (required) +**/ + + +/** + * required setup + */ +require_once( LIBERTY_PKG_CLASS_PATH.'LibertyComment.php' ); + +global $commentsLib, $gBitSmarty, $gBitSystem, $gBitThemes; + +$postComment = array(); +$formfeedback = array( 'error' => array() ); +$gBitSmarty->assignByRef( 'formfeedback', $formfeedback ); + +// make sure that we don't feed ajax comments if we don't have javascript enabled +if( !$gBitThemes->isJavascriptEnabled() ) { + $gBitSystem->setConfig( 'comments_ajax', 'n' ); +} + +if( @BitBase::verifyId( $_REQUEST['delete_comment_id'] )) { + $deleteComment = new LibertyComment($_REQUEST['delete_comment_id']); + // make sure we're loaded up before we delete + $deleteComment->loadComment(); + if( $deleteComment->isValid() && $gContent->hasUserPermission( 'p_liberty_admin_comments' )) { + // delete entire thread + $deleteComment->expunge(); + } +} + +if( @BitBase::verifyId( $_REQUEST['post_comment_id'] ) && $gContent->hasUserPermission( 'p_liberty_post_comments' )) { + $post_comment_id = $_REQUEST['post_comment_id']; + $editComment = new LibertyComment( $post_comment_id ); + //if we are passed a comment id but not going to store it then turn off ajax + if( !isset( $_REQUEST['post_comment_submit'] ) && !isset( $_REQUEST['post_comment_cancel'] )){ + //even if ajax is on - we force it off in this case + $gBitSmarty->assign( 'comments_ajax', FALSE ); + } + + if( $editComment->mInfo['content_id'] ) { + if( $editComment->userCanUpdate( $gContent )) { + $postComment['data'] = $editComment->mInfo['data']; + $postComment['title'] = $editComment->mInfo['title']; + } else { + $formfeedback['error'][] = "You do not have permission to edit this comment."; + $editComment = NULL; + $post_comment_id = NULL; + } + } else { + $formfeedback['error'][] = "Comment does not exist."; + $editComment = NULL; + $post_comment_id = NULL; + } +} else { + $post_comment_id = NULL; + $editComment = NULL; +} +$gBitSmarty->assign('post_comment_id', $post_comment_id); + +// Store comment posts +if( !empty( $_REQUEST['post_comment_submit'] ) && $gContent->hasUserPermission( 'p_liberty_post_comments' )) { + + // check for !anon_post before logging in (auto-fill can hork things up) + if( empty( $_REQUEST['anon_post'] ) && !empty( $_REQUEST['login_email'] ) && !empty( $_REQUEST['login_password'] ) ) { + $gBitUser->login( $_REQUEST['login_email'], $_REQUEST['login_password'] ); + if( !empty( $gBitUser->mErrors['login'] ) ) { + $formfeedback['error'][] = $gBitUser->mErrors['login']; + } + } else { + if( !empty($_REQUEST['comment_name'] )) { + $_REQUEST['anon_name'] = $_REQUEST['comment_name']; + } + } + + // this commentsParentId is some crazy ass business - lets prepare for the day when this can be removed + // there are references to it in LibertyComments::verifyComments as well + $_REQUEST['comments_parent_id'] = $commentsParentId; + + $storeComment = new LibertyComment( @BitBase::verifyId( $editComment->mCommentId ) ? $editComment->mCommentId : NULL ); + + if( empty( $formfeedback['error'] ) && $storeComment->storeComment( $_REQUEST )) { + // store successful + $storeComment->loadComment(); + if( empty( $_REQUEST['post_comment_id'] ) && $gBitSystem->isPackageActive( 'switchboard' ) ) { + // A new comment, and we have switchboard to send notifications + global $gSwitchboardSystem; + // Draft the message: + $message['subject'] = tra( 'New comment on:' ).' '.$gContent->getTitle().' @ '.$gBitSystem->getConfig( 'site_title' ); + $message['message'] = tra('A new message was posted to ').' '.$gContent->getTitle()."<br/>\n".$gContent->getDisplayUri()."<br/>\n" + .'/----- '.tra('Here is the message')." -----/<br/>\n<br/>\n".'<h2>'.$storeComment->getTitle()."</h2>\n".tra('By').' '.$gBitUser->getDisplayName()."\n<p>".$storeComment->getParsedData().'</p>'; + $gSwitchboardSystem->sendEvent('My Content', 'new comment', $gContent->mContentId, $message ); + } + $postComment = NULL; + } else { + // store fails handle errors and preview + $formfeedback['error']=array_merge( $formfeedback['error'], $storeComment->mErrors ); + $postComment['data'] = !empty( $_REQUEST['comment_data'] ) ? $_REQUEST['comment_data'] : ''; + $postComment['title'] = !empty( $_REQUEST['comment_title'] ) ? $_REQUEST['comment_title'] : ''; + if( !empty( $_REQUEST['comment_name'] ) ) { + $postComment['anon_name'] = $_REQUEST['comment_name']; + } + + $_REQUEST['post_comment_request'] = TRUE; + //this is critical and triggers other settings if store fails - do not remove without looking at what preview effects + $_REQUEST['post_comment_preview'] = TRUE; + } +} elseif(!empty($_REQUEST['post_comment_request']) && !$gContent->hasUserPermission( 'p_liberty_post_comments' )) { + $formfeedback['warning']="You don't have permission to post comments."; +} + +// $post_comment_request is a flag indicating whether or not to display the comment input form +if( empty( $_REQUEST['post_comment_request'] ) && !$gBitSystem->isFeatureActive( 'comments_auto_show_form' ) ) { + $post_comment_request = NULL; +} elseif( $gContent->hasUserPermission( 'p_liberty_post_comments' ) ) { + $post_comment_request = TRUE; + // force off ajax attachments which does not work for comments attachments + if( $gBitSystem->isFeatureActive( 'comments_allow_attachments' ) && $gBitSystem->getConfig( 'liberty_attachment_style') == 'ajax' ){ + $gBitSystem->setConfig( 'liberty_attachment_style', 'standard' ); + } +} + +// in anticipation of mainlining LCConfig package - enable comment format configuration +// hack because comments does not have edit service -wjames5 +if( $gBitSystem->isPackageActive( 'lcconfig' ) ){ + $spoofHash = array(); + lcconfig_content_edit( new LibertyComment(), $spoofHash ); +} + +if( !empty( $_REQUEST['post_comment_request'] ) && $_REQUEST['post_comment_request'] == 'y' && !$gContent->hasUserPermission( 'p_liberty_post_comments' ) ) { + $gBitSystem->fatalPermission( 'p_liberty_post_comments' ); +} +$gBitSmarty->assignByRef('post_comment_request', $post_comment_request); + +if( !empty( $_REQUEST['post_comment_cancel'] ) ) { + $postComment = NULL; +} + +// $post_comment_preview is a flag indicating that the user wants to preview their comment prior to saving it +if( !empty( $_REQUEST['post_comment_preview'] )) { + if( isset( $_REQUEST['no_js_preview'] ) && $_REQUEST['no_js_preview']=="y" ) { + $no_js_preview = $_REQUEST['no_js_preview']; + + //even if ajax is on - we force it off in this case + $gBitSmarty->assign( 'comments_ajax', FALSE ); + } else { + $no_js_preview = "n"; + } + + $gBitSmarty->assignByRef( 'no_js_preview', $no_js_preview ); + + $postComment['user_id'] = $gBitUser->mUserId; + $postComment['title'] = $_REQUEST['comment_title']; + if( !empty( $_REQUEST['comment_name'] )) { + $postComment['anon_name'] = $_REQUEST['comment_name']; + } + $postComment['data'] = BitBase::getParameter( $_REQUEST, 'comment_data' ); + $postComment['format_guid'] = empty( $_REQUEST['format_guid'])? $gBitSystem->getConfig( 'default_format' ) : $_REQUEST['format_guid']; + $postComment['parsed_data'] = LibertyComment::parseDataHash( $postComment ); + $postComment['created'] = time(); + $postComment['last_modified'] = time(); + $gBitSmarty->assign('post_comment_preview', TRUE); +} + +// $post_comment_reply_id is the content_id which a post is replying to +if( @BitBase::verifyId( $_REQUEST['post_comment_reply_id'] )) { + $post_comment_reply_id = $_REQUEST['post_comment_reply_id']; + $tmpComment = new LibertyComment( NULL, $post_comment_reply_id ); + if( !empty( $_REQUEST['quote'] )) { + $postComment['data'] = $tmpComment->getQuoted(); + } + if( preg_match( '/^' . tra( 'Re:' ) . '/', $tmpComment->mInfo['title'] )) { + $comment_prefix = ''; + } else { + $comment_prefix = tra( 'Re:' ) . " "; + } + + //this always overrides the title with "Re: Parent Title" -- not sure what it really should do so I put in this conditional for previews + if( !isset( $_REQUEST['comment_title'] )) { + $postComment['title'] = $comment_prefix.$tmpComment->mInfo['title']; + } + + $gBitSmarty->assign( 'post_comment_reply_id', $post_comment_reply_id ); +} + +if( $gContent->hasUserPermission( 'p_liberty_read_comments' )) { + + if( !empty( $_SESSION['liberty_comments_per_page'] )) { + $maxComments = $_SESSION['liberty_comments_per_page']; + } else { + $maxComments = $gBitSystem->getConfig( 'comments_per_page', 10 ); + } + + if( !empty( $_REQUEST["comments_maxComments"] )) { + $maxComments = $_REQUEST["comments_maxComments"]; + $comments_at_top_of_page = 'y'; + $_SESSION['liberty_comments_per_page'] = $maxComments; + } + + if( !empty( $_SESSION['liberty_comments_ordering'] )) { + $comments_sort_mode = $_SESSION['liberty_comments_ordering']; + } else { + $comments_sort_mode = $gBitSystem->getConfig( 'comments_default_ordering', 'commentDate_desc' ); + } + + if( !empty( $_REQUEST["comments_sort_mode"] )) { + $comments_sort_mode = $_REQUEST["comments_sort_mode"]; + $comments_at_top_of_page = 'y'; + $_SESSION['liberty_comments_ordering'] = $comments_sort_mode; + } + + if( !empty( $_SESSION['liberty_comments_display_mode'] )) { + $comments_display_style = $_SESSION['liberty_comments_display_mode']; + } else { + $comments_display_style = $gBitSystem->getConfig( 'comments_default_display_mode', 'threaded' ); + } + + if( !empty( $_REQUEST["comments_style"] ) ) { + $comments_display_style = $_REQUEST["comments_style"]; + $comments_at_top_of_page = 'y'; + $_SESSION['liberty_comments_display_mode'] = $comments_display_style; + } + + if( !empty( $_REQUEST['comment_page'] ) || !empty( $_REQUEST['post_comment_request'] ) ) { + $comments_at_top_of_page = 'y'; + } + $commentOffset = !empty( $_REQUEST['comment_page'] ) ? ($_REQUEST['comment_page'] - 1) * $maxComments : 0; + + if( empty( $gComment )) { + $gComment = new LibertyComment(); + } + + $currentPage = !empty( $_REQUEST['comment_page'] ) ? $_REQUEST['comment_page'] : 1; + if( $currentPage < 1 ) { + $currentPage = 1; + } + + # logic to support displaying a single comment -- used when we need a URL pointing to a comment + if( !empty( $_REQUEST['view_comment_id'] )) { + $commentOffset = $gComment->getNumComments_upto( $_REQUEST['view_comment_id'] ); +# echo "commentOffset =$commentOffset= maxComments=$maxComments=\n"; + $comments_sort_mode = 'commentDate_asc'; + $comments_display_style = 'flat'; + $comments_at_top_of_page = 'y'; + $maxComments = 1; + $currentPage = ceil( $commentOffset + 1 / $maxComments ); + } else { + $commentOffset = ( $currentPage - 1 ) * $maxComments; + } + + + // $commentsParentId is the content_id which the comment tree is attached to + if( !@BitBase::verifyId( $commentsParentId ) ) { + $comments = array(); + $numComments = 0; + } else { + if( @BitBase::verifyId( $commentsParentIds ) ) { + $parents = $commentsParentIds; + } else { + $parents = $commentsParentId; + } + // pass in a reference to the root object so that we can do proper permissions checks + if ( is_object( $gContent )) { + $gComment->mRootObj = $gContent; + } + $numComments = $gComment->getNumComments( $commentsParentId ); + if ($commentOffset > $numComments) { + $commentOffset = $numComments / $maxComments; + $currentPage = ceil( $commentOffset+1 / $maxComments ); + } + $comments = $gComment->getComments( $parents, $maxComments, $commentOffset, $comments_sort_mode, $comments_display_style ); + } + + if( $comments_display_style == 'flat' ) { + $commentsTree = $comments; + } else { + $commentsTree = array(); + foreach( $comments as $id => $node ){ + if( !empty( $comments[ $node['parent_id'] ] )) { + $comments[ $node['parent_id'] ]['children'][$id] = &$comments[$id]; + } + if( $node['parent_id'] == $node['root_id'] || empty( $comments[ $node['parent_id'] ] )) { + $comments[$id]['level'] = 0; + $commentsTree[$id] = &$comments[$id]; + } + } + } + + $gBitSmarty->assignByRef( 'comments', $commentsTree ); + $gBitSmarty->assign( 'maxComments', $maxComments ); + + $numCommentPages = ceil( $numComments / $maxComments ); + $comments_return_url = $comments_return_url.( !strpos( $comments_return_url, '?' ) ? '?' : '' ); + + // libertypagination smarty function setup + $commentsPgnHash = array( + 'numPages' => $numCommentPages, + 'pgnName' => 'comment_page', + 'page' => $currentPage, + 'comment_page' => $currentPage, + 'url' => $comments_return_url, + 'comments_page' => ( empty( $comments_on_separate_page ) ? FALSE : $comments_on_separate_page ), + 'ianchor' => 'editcomments', + ); + $gBitSmarty->assignByRef( 'commentsPgnHash', $commentsPgnHash ); + $gBitSmarty->assignByRef( 'postComment', $postComment ); + $gBitSmarty->assignByRef( 'gComment', $gComment ); + + $gBitSmarty->assign( 'currentTimestamp', time() ); + $gBitSmarty->assign( 'comments_return_url', $comments_return_url ); + $gBitSmarty->assign( 'comments_at_top_of_page', ( isset( $comments_at_top_of_page ) && $gBitSystem->getConfig( 'comments_reorganise_page_layout', 'n' ) == 'y' ) ? $comments_at_top_of_page : NULL ); + $gBitSmarty->assign( 'comments_style', $comments_display_style ); + $gBitSmarty->assign( 'comments_sort_mode', $comments_sort_mode ); + $gBitSmarty->assign( 'textarea_id', 'commentpost' ); + $gBitSmarty->assign( 'comments_count', $numComments ); + + // @TODO get this shit out of here - boards and any other package ridding on comments should make use of services + if( $gBitSystem->isPackageActive( 'boards' )) { + require_once(BOARDS_PKG_CLASS_PATH.'BitBoardTopic.php'); + } + + // @TODO get this shit out of here - boards and any other package ridding on comments should make use of services + // this clearly can go in an edit service, but need to be careful since comments currently does not call edit service - have to check what doing so might trigger. + if( !empty( $_REQUEST['post_comment_request'] )) { + if( $gBitSystem->isPackageActive( 'boards' ) + && ( + BitBoardTopic::isLockedMsg( @BitBase::verifyId( $storeComment->mInfo['parent_id'] ) + ? $storeComment->mInfo['parent_id'] : ( !@BitBase::verifyId( $_REQUEST['post_comment_reply_id'] ) + ? $commentsParentId : $_REQUEST['post_comment_reply_id'] )) + ) + ) { + unset( $_REQUEST['post_comment_request'] ); + unset( $_GET['post_comment_request'] ); + unset( $_POST['post_comment_request'] ); + $formfeedback['warning']="The selected Topic is Locked posting is disabled"; + } + } + +} diff --git a/includes/content_history_inc.php b/includes/content_history_inc.php new file mode 100644 index 0000000..5b1da31 --- /dev/null +++ b/includes/content_history_inc.php @@ -0,0 +1,86 @@ +<?php +/** + * @version $Revision$ + * @package liberty + * @subpackage functions + */ + + +$gBitSmarty->assign( 'source', 0 ); +// If we have to include a preview please show it +$gBitSmarty->assign( 'preview', FALSE ); +$gBitSmarty->assign( 'compare', 'n' ); +$gBitSmarty->assign( 'diff2', 'n' ); + +if( isset( $_REQUEST["delete"] ) && isset( $_REQUEST["hist"] )) { + foreach( array_keys( $_REQUEST["hist"] ) as $version ) { + $gContent->expungeVersion( $version ); + } + +} elseif( isset( $_REQUEST['source'] )) { + $gBitSmarty->assign( 'source', $_REQUEST['source'] ); + if( $_REQUEST['source'] == 'current' ) { + $gBitSmarty->assign( 'sourcev', nl2br( htmlentities( $gContent->mInfo["data"] ))); + } else { + $version = $gContent->getHistory( $_REQUEST["source"] ); + $gBitSmarty->assign( 'sourcev', nl2br( htmlentities( $version["data"][0]["data"] ))); + } + +} elseif( @BitBase::verifyId( $_REQUEST["preview"] )) { + if( $version = $gContent->getHistory( $_REQUEST["preview"] )) { + $version['data'][0]['no_cache'] = TRUE; + $version['data'][0]['parsed_data'] = LibertyContent::parseDataHash( $version["data"][0], $gContent ); + $gBitSmarty->assignByRef( $smartyContentRef, $version['data'][0] ); + $gBitSmarty->assignByRef( 'version', $_REQUEST["preview"] ); + } + +} elseif( @BitBase::verifyId( $_REQUEST["diff2"] ) ) { + $from_version = $_REQUEST["diff2"]; + $from_page = $gContent->getHistory( $from_version ); + $from_lines = explode( "\n",$from_page["data"][0]["data"] ); + if( isset( $_REQUEST["diff_to"] ) && $_REQUEST["diff_to"] != $gContent->mInfo["version"] ) { + $to_version = $_REQUEST["diff_to"]; + $to_page = $gContent->getHistory( $to_version ); + $to_lines = explode( "\n",$to_page["data"][0]["data"] ); + } else { + $to_version = $gContent->mInfo["version"]; + $to_lines = explode( "\n",$gContent->mInfo["data"] ); + } + /** + * run 'pear install Text_Diff' to install the library, + */ + if( $gBitSystem->isFeatureActive( 'liberty_inline_diff' ) && @include_once( 'Text/Diff.php' )) { + include_once( 'Text/Diff/Renderer/inline.php' ); + $diff = new Text_Diff( $from_lines, $to_lines ); + $renderer = new Text_Diff_Renderer_inline(); + $html = $renderer->render( $diff ); + } else { + include_once( UTIL_PKG_INCLUDE_PATH.'diff.php'); + $diffx = new WikiDiff( $from_lines,$to_lines ); + $fmt = new WikiUnifiedDiffFormatter; + $html = $fmt->format( $diffx, $from_lines ); + } + $gBitSmarty->assign( 'diffdata', $html ); + $gBitSmarty->assign( 'diff2', 'y' ); + $gBitSmarty->assign( 'version_from', $from_version ); + $gBitSmarty->assign( 'version_to', $to_version ); + +} elseif( @BitBase::verifyId( $_REQUEST["compare"] )) { + $from_version = $_REQUEST["compare"]; + $from_page = $gContent->getHistory( $from_version ); + $from_page['data'][0]['no_cache'] = TRUE; + $gBitSmarty->assign( 'compare', 'y' ); + $gBitSmarty->assign( 'diff_from', LibertyContent::parseDataHash( $from_page['data'][0], $gContent ) ); + $gBitSmarty->assign( 'diff_to', $gContent->getParsedData() ); + $gBitSmarty->assignByRef( 'version_from', $from_version ); + +} elseif( @BitBase::verifyId( $_REQUEST["rollback"] )) { + $gContent->verifyUserPermission( !empty( $rollbackPerm ) ? $rollbackPerm : $gContent->mUpdateContentPerm ); + if( !isset( $_REQUEST["rollback_comment"] )) { + $_REQUEST["rollback_comment"] = ''; + } + if( $gContent->rollbackVersion( $_REQUEST["rollback"], $_REQUEST["rollback_comment"] )) { + bit_redirect( $gContent->getDisplayUrl() ); + } +} + diff --git a/includes/display_content_inc.php b/includes/display_content_inc.php new file mode 100644 index 0000000..62c0e9f --- /dev/null +++ b/includes/display_content_inc.php @@ -0,0 +1,17 @@ +<?php +/** + * display_content_inc + * + * @author spider <spider@steelsun.com> + * @version $Revision$ + * @package liberty + * @subpackage functions + */ + + global $gBitSmarty, $gBitSystem, $gContent; + + $gBitSmarty->assignByRef( 'pageInfo', $gContent->mInfo ); + + $gBitSystem->display( 'bitpackage:liberty/display_content.tpl' , NULL, array( 'display_mode' => 'display' )); + +?> diff --git a/includes/display_structure_inc.php b/includes/display_structure_inc.php new file mode 100644 index 0000000..4b3844b --- /dev/null +++ b/includes/display_structure_inc.php @@ -0,0 +1,22 @@ +<?php +/** + * display_structure_inc + * + * @author spider <spider@steelsun.com> + * @version $Revision$ + * @package liberty + * @subpackage functions + */ + +/** + * required setup + */ +global $gContent; +include_once( LIBERTY_PKG_INCLUDE_PATH.'lookup_content_inc.php' ); +if( is_object( $gContent ) && $gContent->isValid() ) { + $gBitSystem->setBrowserTitle( $gStructure->getRootTitle().' : '.$gContent->getTitle() ); + $gBitSystem->setCanonicalLink( $gContent->getDisplayUrl() ); + include $gContent->getRenderFile(); +} else { + $gBitSystem->fatalError( tra( 'Page cannot be found' ), NULL, NULL, HttpStatusCodes::HTTP_GONE ); +} diff --git a/includes/edit_help_inc.php b/includes/edit_help_inc.php new file mode 100644 index 0000000..1e31a4d --- /dev/null +++ b/includes/edit_help_inc.php @@ -0,0 +1,68 @@ +<?php +/** + * $Id$ + * edit_help_inc + * + * @author spider <spider@steelsun.com> + * @version $Revision$ + * @package liberty + * @subpackage functions + */ + +/** + * required setup + */ +global $gLibertySystem, $gBitSmarty; + +$inEditor = TRUE; // Required by PluginHelp to Determin Executed in an Editor + +$dataplugins = array_merge( $gLibertySystem->getPluginsOfType( DATA_PLUGIN ), $gLibertySystem->getPluginsOfType( FILTER_PLUGIN )); +$formatplugins = $gLibertySystem->getPluginsOfType( FORMAT_PLUGIN ); +$mimeplugins = $gLibertySystem->getPluginsOfType( MIME_PLUGIN ); + +// allow mime plugins to append help to the attachment plugin +foreach( $mimeplugins as $guid => $plugin ) { + if( $func = $gLibertySystem->getPluginFunction( $guid, 'help_function' )) { + $plugin['exthelp'] = $func(); + $mimeplugins[$guid]= $plugin; + } else { + unset( $mimeplugins[$guid] ); + } +} + +// refine data plugins and add help where available +foreach( $dataplugins as $guid => $plugin ) { + if( !empty( $plugin['description'] ) && !empty( $plugin['syntax'] )) { + $plugin["plugin_guid"] = preg_replace( "/^(data|filter)/", "", $guid ); + $plugin["exthelp"] = !empty( $plugin['help_function'] ) && $gLibertySystem->getPluginFunction( $guid, 'help_function' ) ? $plugin['help_function']() : ''; + $dataplugins[$guid] = $plugin; + } else { + unset( $dataplugins[$guid] ); + } +} + +foreach( array_keys( $formatplugins ) as $guid ) { + // check to see if we have some format syntax help + if( is_file( LIBERTY_PKG_PATH."templates/help_format_{$guid}_inc.tpl" )) { + $formatplugins[$guid]['format_help'] = "bitpackage:liberty/help_format_{$guid}_inc.tpl"; + if( is_file( LIBERTY_PKG_INCLUDE_PATH.'help_format_{$guid}_inc.php' )) { + include_once( LIBERTY_PKG_INCLUDE_PATH.'help_format_{$guid}_inc.php' ); + } + } +} + +if( !empty( $formatplugins ) ) { + usort( $formatplugins, 'usort_by_title' ); + $gBitSmarty->assignByRef( 'formatplugins', $formatplugins ); +} + +if( !empty( $mimeplugins ) ) { + usort( $mimeplugins, 'usort_by_title' ); + $gBitSmarty->assignByRef( 'mimeplugins', $mimeplugins ); +} + +if( !empty( $dataplugins ) ) { + usort( $dataplugins, 'usort_by_title' ); + $gBitSmarty->assignByRef( 'dataplugins', $dataplugins ); +} +?> diff --git a/includes/edit_storage_inc.php b/includes/edit_storage_inc.php new file mode 100644 index 0000000..3d40b0f --- /dev/null +++ b/includes/edit_storage_inc.php @@ -0,0 +1,55 @@ +<?php +/** + * @version $Header$ + * + * edit_storage_inc + * @author spider <spider@steelsun.com> + * @package liberty + * @subpackage functions + */ +global $gBitSmarty, $gContent, $gBitUser, $gBitSystem, $gBitThemes, $gLibertySystem; + +// if we have active plugins with an upload function, we call them: +foreach( $gLibertySystem->getPluginFunctions( 'upload_function' ) as $guid => $func ) { + $func( $gContent ); +} + +// set up base arguments +$getArgs = explode( '&', $_SERVER['QUERY_STRING'] ); +$attachmentBaseArgs = ''; +foreach( $getArgs as $arg ) { + $parts = explode( '=', $arg ); + if( $parts[0] != 'deleteAttachment' ) { + $attachmentBaseArgs .= $arg."&"; + } +} +$gBitSmarty->assign( 'attachmentBaseArgs', $attachmentBaseArgs ); + +// delete attachment if requested +if( !empty( $_REQUEST['deleteAttachment'] )) { + + // $gContent is empty when we're editing our personal webpage + // this is a brutish hack but it seems to work without adverse effeects + if( empty( $gContent )) { + $gContent = $gBitUser; + } + + $attachmentId = $_REQUEST['deleteAttachment']; + $attachmentInfo = $gContent->getAttachment( $attachmentId ); + + // the second part of this check seems odd (never used?) to me, but I'll leave it in for now - spiderr 10/17/2007 + if( $gContent->hasAdminPermission() || ( $gContent->isOwner( $attachmentInfo ) && $gBitUser->hasPermission( 'p_liberty_delete_attachment' ))) { + $gContent->expungeAttachment( $attachmentId ); + } + + // in case we have deleted attachments + // seems like there should be a better way to do this -- maybe original assign should have been by reference? + $gBitSmarty->clearAssign( 'gContent' ); + $gBitSmarty->assign( 'gContent', $gContent ); +} + +// make sure js is being loaded +if( $gBitSystem->getConfig( 'liberty_attachment_style' ) == 'ajax' ) { + $gBitThemes->loadJavascript( LIBERTY_PKG_PATH.'scripts/LibertyAttachment.js', TRUE ); +} +?> diff --git a/includes/edit_structure_inc.php b/includes/edit_structure_inc.php new file mode 100644 index 0000000..5fa1b4c --- /dev/null +++ b/includes/edit_structure_inc.php @@ -0,0 +1,126 @@ +<?php +/** + * edit_structure_inc + * + * @author Christian Fowler> + * @version $Revision$ + * @package liberty + * @subpackage functions + */ + +// Copyright (c) 2004, Christian Fowler, 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. + +/** + * required setup + */ +require_once( '../kernel/setup_inc.php' ); +include_once( LIBERTY_PKG_CLASS_PATH.'LibertyStructure.php'); + +if( !@BitBase::verifyId( $_REQUEST["structure_id"] ) ) { + $gBitSystem->fatalError( tra( "No structure indicated" )); +} else { + global $gStructure; + $gStructure = new LibertyStructure( $_REQUEST["structure_id"] ); + $gStructure->load(); + + // order matters for these conditionals + if( empty( $gStructure ) || !$gStructure->isValid() ) { + $gBitSystem->fatalError( tra( 'Invalid structure' )); + } + + if( $gStructure->mInfo['root_structure_id'] == $gStructure->mInfo['structure_id'] ) { + $rootStructure = &$gStructure; + } else { + $rootStructure = new LibertyStructure( $gStructure->mInfo['root_structure_id'] ); + $rootStructure->load(); + $rootStructure->loadNavigation(); + $rootStructure->loadPath(); + } + if( empty( $gContent ) ) { + $gContent = LibertyContent::getLibertyObject( $gStructure->getField( 'content_id' ) ); + $gContent->verifyUpdatePermission(); + } + $gBitSmarty->assignByRef( 'gStructure', $gStructure ); + $gBitSmarty->assign( 'editingStructure', TRUE ); + $gBitSmarty->assign('structureInfo', $gStructure->mInfo); + + // Store the actively stored structure name + $gBitUser->storePreference( 'edit_structure_name', $rootStructure->mInfo['title'] ); + $gBitUser->storePreference( 'edit_structure_id', $rootStructure->mStructureId ); + + if( ( isset( $_REQUEST["action"] ) && ( $_REQUEST["action"] == 'remove' ) ) || !empty( $_REQUEST["confirm"] ) ) { + $gBitUser->verifyTicket(); + if( $_REQUEST["action"] == 'remove' && ($gBitThemes->isAjaxRequest() || !empty( $_REQUEST["confirm"] )) ) { + $gBitUser->verifyTicket(); + if( $gStructure->removeStructureNode( $_REQUEST["structure_id"], false ) ) { + if( $gBitThemes->isAjaxRequest() ) { + $feedback['success'] = tra( "removed from" ).' '.$gContent->getContentTypeName(); + } else { + bit_redirect( $_SERVER['SCRIPT_NAME'].'?structure_id='.$gStructure->mInfo["parent_id"] ); + } + } else { + $feedback['error'] = $gStructure->mErrors; + } + $gBitSmarty->assignByRef('feedback', $feedback); + } elseif( $_REQUEST["action"] == 'remove' ) { + $gBitSystem->setBrowserTitle( tra('Confirm removal of ').$gContent->getTitle() ); + $formHash['action'] = 'remove'; + $formHash['remove'] = TRUE; + $formHash['structure_id'] = $_REQUEST['structure_id']; + $msgHash = array( + 'label' => tra('Remove content from Structure'), + 'confirm_item' => $gContent->getTitle().tra('and any subitems'), + 'warning' => tra('This will remove the content from the structure but will <strong>not</strong> modify or remove the content itself.'), + ); + $gBitSystem->confirmDialog( $formHash,$msgHash ); + } + } elseif (isset($_REQUEST["move_node"])) { + if ($_REQUEST["move_node"] == '1') { + $gStructure->moveNodeWest(); + } elseif ($_REQUEST["move_node"] == '2') { + $gStructure->moveNodeNorth(); + } elseif ($_REQUEST["move_node"] == '3') { + $gStructure->moveNodeSouth(); + } elseif ($_REQUEST["move_node"] == '4') { + $gStructure->moveNodeEast(); + } + bit_redirect( $_SERVER['SCRIPT_NAME'].'?structure_id='.$gStructure->mInfo["structure_id"] ); + } elseif( !empty( $_REQUEST['submit_structure'] ) ) { + if( $gStructure->storeStructure( $_REQUEST ) ) { + $feedback['success'] = tra( "Your changes were successfully saved." ); + } else { + $feedback['error'] = $gStructure->mErrors; + } + } elseif (isset($_REQUEST["create"]) || (isset( $_REQUEST["action"] ) && $_REQUEST["action"] == 'add') ) { + $structureHash['root_structure_id'] = $rootStructure->mStructureId; + $structureHash['parent_id'] = $_REQUEST['structure_id']; + + $after = null; + if (isset($_REQUEST['after_ref_id'])) { + $structureHash['after_ref_id'] = $_REQUEST['after_ref_id']; + } + if (!(empty($_REQUEST['name']))) { + $gStructure->s_create_page($_REQUEST["structure_id"], $after, $_REQUEST["name"], ''); + } elseif(!empty($_REQUEST['content'])) { + foreach ($_REQUEST['content'] as $conId ) { + $structureHash['content_id'] = $conId; + if( $new_structure_id = $gStructure->storeNode( $structureHash ) ) { + $structureHash['after_ref_id'] = $new_structure_id; + $feedback['success'] = tra( "added to" ).' '.$gContent->getContentTypeName(); + } else { + $feedback['failure'] = $gStructure->mErrors; + } + } + } + } + + $structureTocId = $rootStructure->mStructureId; + $gBitSmarty->assign( 'structureToc', $rootStructure->getToc() ); + $gBitSmarty->assign( 'structureTocId', $structureTocId ); + $gBitSmarty->assignByRef('feedback', $feedback); +} + $gBitSmarty->assign( 'editingStructure', FALSE ); + +?> diff --git a/includes/get_content_list_inc.php b/includes/get_content_list_inc.php new file mode 100644 index 0000000..a5bab92 --- /dev/null +++ b/includes/get_content_list_inc.php @@ -0,0 +1,93 @@ +<?php +/** + * get_content_list + * + * @author Christian Fowler> + * @version $Revision$ + * @package liberty + * @subpackage functions + */ + +/** + * required setup + */ +require_once( LIBERTY_PKG_CLASS_PATH.'LibertyContent.php' ); +global $gContent; +global $gLibertySystem; + +if( empty( $gContent ) || !is_object( $gContent ) ) { + $gContent = new LibertyContent(); +} + +$contentTypeGuids = array(); +if( !empty( $_REQUEST['content_type_guid'] )) { + if( !is_array( $_REQUEST['content_type_guid'] )) { + $guids = explode( ",", $_REQUEST['content_type_guid'] ); + } else { + $guids = $_REQUEST['content_type_guid']; + } + /** + * if an empty string was passed in an array (likely since it is used for ALL) then the user has requested all so return all + * even if they have requested additional content types too - ALL is ALL + * this check is reversed in that if no empty string in the array then we pass the array of content types to be limited on + **/ + if( !in_array( "", $guids ) ){ + $contentTypeGuids = $guids; + } +} + +// get_content_list_inc doesn't use $_REQUEST parameters as it might not be the only list in the page that needs sorting and limiting +if( empty( $contentListHash ) ) { + $contentListHash = array( + 'content_type_guid' => $contentSelect = empty( $_REQUEST['content_type_guid'] ) ? NULL : $contentTypeGuids, + // pagination offset + 'offset' => !empty( $offset_content ) ? $offset_content : NULL, + // maximum number of records displayed on a page + 'max_records' => !empty( $max_content ) ? $max_content : ( !empty( $_REQUEST['max_records'] ) ? $_REQUEST['max_records'] : 100 ), + // sort by this: <table column>_asc (or _desc) + 'sort_mode' => !empty( $content_sort_mode ) ? $content_sort_mode : 'title_asc', + // limit the result to this set + 'find' => !empty( $_REQUEST["find"] ) ? $_REQUEST["find"] : NULL, + // display this page number - replaces antiquated offset + 'page' => !empty( $_REQUEST["list_page"] ) ? $_REQUEST["list_page"] : NULL, + // only display content by this user + 'user_id' => @BitBase::verifyId( $_REQUEST['user_id'] ) ? $_REQUEST['user_id'] : NULL, + // only display content modified more recently than this (UTC timestamp) + 'from_date' => !empty( $_REQUEST["from_date"] ) ? $_REQUEST["from_date"] : NULL, + // only display content modified before this (UTC timestamp) + 'until_date' => !empty( $_REQUEST["until_date"] ) ? $_REQUEST["until_date"] : NULL, + // get a thumbnail - off by default because it is expensive + 'thumbnail_size' => !empty( $_REQUEST["thumbnail_size"] ) ? $_REQUEST["thumbnail_size"] : NULL, + ); + + if( !empty( $_REQUEST['output'] ) && ( $_REQUEST['output'] == 'json' || $_REQUEST['output'] == 'ajax' ) ) { + foreach( $_REQUEST as $key => $value ) { + if ( !is_array($value) ){ + if( strstr( $value, ',' ) ) { + $_REQUEST[$key] = explode( ",", $value ); + } + } + } + } + + $contentListHash = array_merge( $_REQUEST, $contentListHash ); +} + +// Finally we're ready to get some content +$contentList = $gContent->getContentList( $contentListHash ); + +if( empty( $contentTypes ) ) { + $contentTypes = array( '' => tra( 'All Content' ) ); + foreach( $gLibertySystem->mContentTypes as $cType ) { + $contentTypes[$cType['content_type_guid']] = $gLibertySystem->getContentTypeName( $cType['content_type_guid'], TRUE ); + } + asort( $contentTypes ); +} +global $gBitSystem, $gBitUser; +if( $gBitSystem->isFeatureActive( 'liberty_display_status' ) && $gBitUser->hasPermission( 'p_liberty_view_all_status' )) { + $contentStatuses = $gContent->getAvailableContentStatuses(); + $contentStatuses[''] = 'All Statuses'; + $contentStatuses['not_available'] = 'All but Available'; + $gBitSmarty->assign( 'content_statuses', $contentStatuses ); +} +?> diff --git a/includes/help_format_tikiwiki_inc.php b/includes/help_format_tikiwiki_inc.php new file mode 100644 index 0000000..e6ad141 --- /dev/null +++ b/includes/help_format_tikiwiki_inc.php @@ -0,0 +1,239 @@ +<?php +/** + * help_format_tikiwiki_inc + * + * @author Christian Fowler> + * @version $Revision$ + * @package liberty + * @subpackage functions + */ + +/** + * required setup + */ +global $gBitSystem, $gBitSmarty; +require_once( KERNEL_PKG_PATH.'BitCache.php' ); +$cache = new BitCache( 'liberty/help' ); + +// only regenerate this thing if it's not cached yet +$cacheFile = 'tikiwiki'; +if( $cache->isCached( $cacheFile, filemtime( __FILE__ ))) { + $examples = unserialize( $cache->readCacheFile( $cacheFile )); +} else { + // help for generic options + $tikiwiki = array( + 'Emphasis' => array( + 'Headings' => array( + 'data' => "! heading 1\n!! heading 2\n!!! heading 3", + 'note' => "Number of ! correponds to heading level.", + ), + 'Italics' => array( + 'data' => "''text''", + 'note' => "Two single quotes not one double quote", + ), + 'Underline' => array( + 'data' => "===text===", + ), + 'Coloured Background' => array( + 'data' => "++yellow:text++", + ), + 'Coloured Text' => array( + 'data' => "~~red:text~~", + ), + 'Bold' => array( + 'data' => "__text__", + ), + 'Centered Text' => array( + 'data' => "::text::", + ), + 'Combined' => array( + 'data' => "::__~~red:++yellow:text++~~__::", + 'note' => "When you combine options make sure you open and close in the opposite order analogous to: {[(text)]}", + ), + ), + 'Lists' => array( + 'Unordered Lists' => array( + 'data' => "* First item\n** First subitem\n** Second subitem\n* Second item", + ), + 'Ordered Lists' => array( + 'data' => "# First item\n## First subitem\n## Second subitem\n# Second item", + ), + 'Definition Lists' => array( + 'data' => ";Term: Definition", + ), + ), + 'Links' => array( + 'Wiki Links' => array( + 'data' => "((Wiki Page))", + 'result' => '<a href="#">Wiki Page</a>', + ), + 'Wiki Links + Description' => array( + 'data' => "((Wiki Page|Page Description))", + 'result' => '<a href="#">Page Description</a>', + ), + 'Wiki Links + Anchor + Description' => array( + 'data' => "((Wiki Page#Anchor|Page Description))", + 'result' => '<a href="#">Page Description</a>', + ), + 'External Link' => array( + 'data' => "[http://www.example.com]", + ), + 'External Link + Description' => array( + 'data' => "[http://www.example.com|Description]", + ), + 'External Link + Anchor + Description' => array( + 'data' => "[http://www.example.com/Page#Anchor|Description]", + ), + ), + 'Miscellaneous' => array( + 'Horizontal Rule' => array( + 'data' => '---', + ), + 'Highlighted Bar' => array( + 'data' => '-=text=-', + ), + 'Highlighted Box' => array( + 'data' => "^text\nmore text^", + ), + 'As is Text' => array( + 'data' => "~np~~~yellow:yellow~~\nand\n__bold__ text~/np~", + 'note' => "This text will not be parsed", + ), + 'Pre Parsed' => array( + 'data' => "~pp~~~yellow:yellow~~\nand\n__bold__ text~/pp~", + 'note' => "This text will be treated like code and will not be altered and will be displayed using a monospace font. The same can be achieved by using <pre>text</pre>.", + ), + 'Monospaced Text' => array( + 'data' => "-+text+-", + ), + 'Right to Left' => array( + 'data' => "{r2l}this text is from\nright to left\n{l2r}and back to\nleft to right.", + ), + ), + 'Simple Tables' => array( + 'Simple Table' => array( + 'data' => "|| Row1-Col1 | Row1-Col2\nRow2-Col1 | Row2-Col2 ||", + ), + 'With Headers' => array( + 'data' => "||~ Header1 | Header2\nRow1-Col1 | Row1-Col2\nRow2-Col1 | Row2-Col2 ||", + ), + ), + ); + + if( $gBitSystem->getConfig( 'wiki_tables' ) == 'old' ) { + $tikiwiki['Simple Tables'] = array( + 'Tables' => array( + 'data' => "|| Row1-Col1 | Row1-Col2 || Row2-Col1 | Row2-Col2 ||", + ), + ); + } + + foreach( array_keys( $tikiwiki ) as $section ) { + foreach( $tikiwiki[$section] as $title => $example ) { + if( empty( $example['result'] )) { + $example['format_guid'] = 'tikiwiki'; + $tikiwiki[$section][$title]['result'] = LibertyContent::parseDataHash( $example ); + } + } + } + + // mediawiki type tables + $mediawiki = array( + 'Example 1' => array( + 'data' => +'{| class="table" +|+A Simple Table +|- +! Col 1 !! Col 2 !! Col 3 +|- +| Row1-Col1 || Row1-Col2 || Row1-Col3 +|- +| Row2-Col1 +| Row2-Col2 +| Row2-Col3 +|- +| Row3-Col1 || Row3-Col2 || Row3-Col3 +|}' + ), + 'Example 2' => array( + 'data' => +'{| class="table table-boardered" +|+Multiplication table +|- +! X !! 1 !! 2 !! 3 +|- +! 1 +| 1 || 2 || 3 +|- +! 2 +| 2 || 4 || 6 +|- +! 3 +| 3 || 6 || 9 +|- +! 4 +| 4 || 8 || 12 +|- +! 5 +| 5 || 10 || 15 +|}' + ), + 'Example 3' => array( + 'data' => +'{| class="table table-striped" +|+ Table with alternating rows +|- +| one +| two +|- +| three +| four +|- +| five +| six +|- +| seven +| eight +|- class="success" +| colspan="2" | success +|- class="error" +| colspan="2" | error +|- class="warning" +| colspan="2" | warning +|- class="info" +| colspan="2" | info +|}' + ), + 'Example 4' => array( + 'data' => +'{| class="table" style="background:yellow;color:green" +|+ Table with many colours +|- +| abc +| colspan="2" style="text-align:center;background:lightblue;" | defghi +|- style="background:red;color:white" +| jkl +| mno +| pqr +|- +| style="font-weight:bold" | stu +| style="background:silver" | vwx +| yz +|}' + ), + ); + + // parse tables + foreach( $mediawiki as $title => $example ) { + if( empty( $example['result'] )) { + $example['format_guid'] = 'tikiwiki'; + $mediawiki[$title]['result'] = LibertyContent::parseDataHash( $example ); + } + } + + $examples['tikiwiki'] = $tikiwiki; + $examples['mediawiki'] = $mediawiki; + $cache->writeCacheFile( $cacheFile, serialize( $examples )); +} +$gBitSmarty->assign( 'examples', $examples ); +?> diff --git a/includes/liberty_lib.php b/includes/liberty_lib.php new file mode 100644 index 0000000..4c6f125 --- /dev/null +++ b/includes/liberty_lib.php @@ -0,0 +1,939 @@ +<?php +/** + * @package liberty + * @subpackage functions + */ + +// ================== Liberty Plugin Parsing ================== +/** + * This crazy function will parse all the data plugin stuff found within any + * parsed text section + * + * @param array $pData Data to be parsed + * @access public + * @return void + */ +function parse_data_plugins( &$pData, &$pReplace, $pCommonObject, $pParseHash ) { + global $gLibertySystem, $gBitSystem; + + // note: $curlyTags[0] is the complete match, $curlyTags[1] is plugin name, $curlyTags[2] is plugin arguments + preg_match_all( "/\{\/?([A-Za-z0-9]+)([^\}]*)\}/", $pData, $curlyTags, PREG_OFFSET_CAPTURE ); + + if( count( $curlyTags[0] ) ) { + // if TRUE, replace only CODE plugin, if false, replace all other plugins + $code_first = TRUE; + + // Process plugins in reverse order, so that nested plugins are handled from the inside out. + $i = count( $curlyTags[0] ) - 1; + while( $i >= 0 ) { + $plugin_start = $curlyTags[0][$i][0]; + $plugin = $curlyTags[1][$i][0]; + // Work out where the plugin starts. This can not be done using the + // positional data from $curlyTags since the position might have + // changed since the last cycle. We therefore need to determine the + // position direclty. - xing - Thursday Nov 01, 2007 22:55:10 CET + //$pos = $curlyTags[0][$i][1]; + $pos = strpos( $pData, $plugin_start ); + $dataTag = strtolower( $plugin ); + // hush up the return of this in case someone uses curly braces to enclose text + $pluginInfo = $gLibertySystem->getPluginInfo( @$gLibertySystem->mDataTags[$dataTag] ) ; + + // only process a standalone unpaired tag or the start tag for a paired tag + if( empty( $paired_close_tag_seen[$dataTag] ) || $paired_close_tag_seen[$dataTag] == 0 ) { + $paired_close_tag_seen[$dataTag] = 1; + } else { + $paired_close_tag_seen[$dataTag] = 0; + } + + $is_opening_tag = FALSE; + if(( empty( $pluginInfo['requires_pair'] ) && ( strtolower( $plugin_start ) != '{/'. $dataTag . '}' )) + || ( strpos( $plugin_start, ' ' ) > 0 ) + || ( strtolower( $plugin_start ) == '{'.$dataTag.'}' && !$paired_close_tag_seen[$dataTag] ) + ) { + $is_opening_tag = TRUE; + } + + if( + // when in CODE parsing mode, replace only CODE plugins + ( ( $code_first && ( $dataTag == 'code' ) ) + // when NOT in CODE parsing mode, replace all other plugins + || ( !$code_first && ( $dataTag <> 'code' ) ) + ) + && !empty( $gLibertySystem->mDataTags[$dataTag] ) + && !empty( $pluginInfo ) + && ( $loadFunc = $gLibertySystem->getPluginFunction( $gLibertySystem->mDataTags[$dataTag], 'load_function' ) ) + && ( $is_opening_tag ) + ) { + + if( $pluginInfo['requires_pair'] ) { + $plugin_end = '{/'.$plugin.'}'; + $pos_end = strpos( strtolower( $pData ), strtolower( $plugin_end ), $pos ); // where plugin data ends + $plugin_end2 = '{'.$plugin.'}'; + $pos_end2 = strpos( strtolower( $pData ), strtolower( $plugin_end2 ), $pos + 1 ); // where plugin data ends + + if( ( $pos_end2 > 0 && $pos_end2 > 0 && $pos_end2 < $pos_end ) || $pos_end === FALSE ) { + $pos_end = $pos_end2; + $plugin_end = $plugin_end2; + } + } else { + $pos_end = $pos + strlen( $curlyTags[0][$i][0] ); + $plugin_end = ''; + } + + // Extract the plugin data + $plugin_data_len = $pos_end - $pos - strlen( $curlyTags[0][$i][0] ); + $plugin_data = substr( $pData, $pos + strlen( $plugin_start ), $plugin_data_len ); + + $arguments = array(); + // Construct argument list array + $paramString = str_replace( '>', '>', trim( $curlyTags[2][$i][0] ) ); + if( preg_match( '/^\(.*=>.*\)$/', $paramString ) ) { + $paramString = preg_replace( '/[\(\)]/', '', $paramString ); + //we have the old style parms like {CODE (in=>1)} + $params = explode( ',', trim( $paramString ) ); + + foreach( $params as $param ) { + // the following str_replace line is to decode the > char when html is turned off + // perhaps the plugin syntax should be changed in 1.8 not to use any html special chars + $parts = preg_split( '/=>?/', $param ); + + if( isset( $parts[0] ) && isset( $parts[1] ) ) { + $name = trim( $parts[0] ); + $arguments[$name] = trim( $parts[1] ); + } + } + } else { + $paramString = trim( $curlyTags[2][$i][0], " \t()" ); + $paramString = str_replace(""", '"', $paramString); + $arguments = parse_xml_attributes( $paramString ); + } + + if( $ret = $loadFunc( $plugin_data, $arguments, $pCommonObject, $pParseHash )) { + $key = "parseprotect".md5( mt_rand() ); + $pReplace[] = array( + 'key' => $key, + 'data' => $ret, + ); + + // don't modify data if $pos is FALSE + if( $pos !== FALSE ) { + $pData = substr_replace( $pData, $key, $pos, $pos_end - $pos + strlen( $plugin_end )); + } + } + } + $i--; + // if we are in CODE parsing mode and list is done, switch to 'parse other plugins' mode and start all over + if( ( $code_first ) && ( $i < 0 ) ) { + $i = count( $curlyTags[0] ) - 1; + $code_first = FALSE; + } + } // while + } +} + +/** + * This function replaces pre- and no-parsed sections with unique keys and + * saves the section contents for later reinsertion. It is needed by + * parse_data_plugins() to extract sections that don't require parsing + * + * @param array $pData data that might contain ~np~ or ~pp~ strings + * @param array $preparsed array that is updated by refrerence with key and data that needs to be substituted later + * @param array $noparsed array that is updated by refrerence with key and data that needs to be substituted later + * @access public + * @return void + */ +function parse_protect( &$pData, &$pReplace ) { + // Find all sections delimited by ~pp~ ... ~/pp~ + preg_match_all( "/\~pp\~(.*?)\~\/pp\~/s", $pData, $preparse ); + if( count( $preparse[0] )) { + foreach( array_unique( $preparse[1] ) as $pp ) { + $aux["key"] = md5( mt_rand() ); + $aux["data"] = "<pre><code>".htmlspecialchars( $pp )."</code></pre>"; + $pReplace[] = $aux; + $pData = str_replace( "~pp~$pp~/pp~", $aux['key'], $pData ); + } + } + + // now remove <pre>...<pre> sections + preg_match_all( "!(<pre[^>]*>)(.*?)(</pre[^>]*>)!si", $pData, $preparse ); + if( count( $preparse[0] )) { + foreach( $preparse[2] as $key => $pre ) { + $aux["key"] = md5( mt_rand() ); + $aux["data"] = $preparse[1][$key].htmlspecialchars( $pre ).$preparse[3][$key]; + $pReplace[] = $aux; + $pData = str_replace( $preparse[1][$key].$pre.$preparse[3][$key], $aux['key'], $pData ); + } + } + + // and now ~np~...~/np~ sections + preg_match_all( "/\~np\~(.*?)\~\/np\~/s", $pData, $noparse ); + if( count( $noparse[0] )) { + foreach( array_unique( $noparse[1] ) as $np ) { + $aux["key"] = md5( mt_rand() ); + $aux["data"] = htmlspecialchars( $np ); + $pReplace[] = $aux; + $pData = str_replace( "~np~$np~/np~", $aux['key'], $pData ); + } + } +} + + +// ================== Liberty Plugin Helper ================== +/** + * pass in the plugin parameters and out comes a hash with usable styling information + * + * @param array $pParamHash + * @access public + * @return hash full of styling goodies + */ +function liberty_plugins_wrapper_style( $pParamHash ) { + global $gBitSystem; + + $ret = array(); + $ret['style'] = $ret['description'] = ''; + + if( !empty( $pParamHash ) && is_array( $pParamHash )) { + // if align is right and text-align isn't set, we'll align that right as well + if( empty( $pParamHash['text-align'] ) && ( !empty( $pParamHash['align'] ) && $pParamHash['align'] == 'right' || !empty( $pParamHash['align'] ) && $pParamHash['align'] == 'right' )) { + $pParamHash['text-align'] = 'right'; + } + + // this defines what the wrapper should be - div or span + // if someone sets this value manually, they know what they are doing + if( empty( $pParamHash['wrapper'] )) { + $pParamHash['wrapper'] = 'div'; + + if( $gBitSystem->isFeatureActive( 'liberty_use_span_wrapper' )) { + // set to 'span' if desired + $pParamHash['wrapper'] = 'span'; + + // force display:block to the "div" if not specified otherwise + if( empty( $pParamHash['display'] )) { + $pParamHash['display'] = "inline-block"; + } + } + } + + foreach( $pParamHash as $key => $value ) { + if( !empty( $value )) { + switch( $key ) { + // description + case 'desc': + $key = 'description'; + case 'description': + $ret[$key] = $value; + break; + // styling + case 'width': + case 'height': + if( preg_match( "/^\d+(em|px|%|pt)$/", trim( $value ))) { + $ret['style'] .= "{$key}:{$value};"; + } elseif( preg_match( "/^\d+$/", $value )) { + $ret['style'] .= "{$key}:{$value}px;"; + } + break; + case 'background': + case 'background-color': + case 'border': + case 'color': + case 'display': + case 'float': + case 'font': + case 'font-family': + case 'font-size': + case 'font-weight': + case 'margin': + case 'overflow': + case 'padding': + case 'text-align': + $ret['style'] .= "{$key}:{$value};"; + break; + // align and float are special + case 'align': + if( $value == 'center' || $value == 'middle' ) { + $ret['style'] .= 'text-align:center;'; + } else { + $ret['style'] .= "float:{$value};"; + } + break; + // default just gets re-assigned + default: + $ret[$key] = $value; + break; + } + } + } + } + + return $ret; +} + + +// ================== Liberty Service Functions ================== +/** + * liberty_content_load_sql + * + * @access public + * @return content load sql + */ +function liberty_content_load_sql( &$pObject, $pParamHash=NULL ) { + global $gBitSystem, $gBitUser; + $ret = array(); + + $hasPerm = ( is_object( $pObject ) && isset( $pObject->hasUserPermission )) ? $pObject->hasUserPermission( 'p_liberty_edit_all_status' ) : $gBitUser->hasPermission( 'p_liberty_edit_all_status' ); + + if( $gBitSystem->isFeatureActive( 'liberty_display_status' ) && !$hasPerm ) { + if(( is_object( $pObject ) && !empty( $pObject->mType['content_type_guid'] ) && $pObject->mType['content_type_guid'] == 'bitcomment' ) + || ( !empty( $pParamHash['include_comments'] ) && $pParamHash['include_comments'] == 'y' )) { + // if we are getting a list of comments then lets check the owner of the comment root and the owner of the content + $ret['join_sql'] = " + INNER JOIN `".BIT_DB_PREFIX."liberty_comments` lcoms ON (lc.`content_id` = lcoms.`content_id`) + INNER JOIN `".BIT_DB_PREFIX."liberty_content` rlcs ON( rlcs.`content_id`=lcoms.`root_id` )"; + $ret['where_sql'] = " AND lc.`content_status_id` < 100 AND ( ( (rlcs.`user_id` = '".$gBitUser->getUserId()."' OR lc.`user_id` = '".$gBitUser->getUserId()."') AND lc.`content_status_id` > -100) OR lc.`content_status_id` > 0 )"; + } else { + // let owner see any of their own content with a status > -100 + $ret['where_sql'] = " AND lc.`content_status_id` < 100 AND ( (lc.`user_id` = '".$gBitUser->getUserId()."' AND lc.`content_status_id` > -100) OR lc.`content_status_id` > 0 )"; + } + } + + // Make sure owner comes out properly for all content + if ($gBitSystem->isFeatureActive('liberty_allow_change_owner') && $gBitUser->hasPermission('p_liberty_edit_content_owner')) { + $ret['select_sql'] = " , lc.`user_id` AS owner_id"; + } + return $ret; +} + +/** + * liberty_content_list_sql + * + * @param array $pParamHash + * @param array $pParamHash['enforce_status'] will add joins to status_id even if user is admin + * @param array $pParamHash['min_status_id'] one less than the minimum status a content can have to be included + * @param array $pParamHash['max_status_id'] one more than the maximum status a content can have to be included + * @param array $pParamHash['min_owner_status_id'] one less than the mimimum status a content can have to be included that is owned by the requester + * @access public + * @return content list sql + */ +function liberty_content_list_sql( &$pObject, $pParamHash=NULL ) { + global $gBitSystem, $gBitUser; + $ret = array(); + + $hasPerm = FALSE; + // enforce_status will require the status limit on everyone including admin and thus we can ignore permission checks + if( !isset( $pParamHash['enforce_status'] )) { + $hasPerm = ( is_object( $pObject ) && method_exists( $pObject, 'hasUserPermission' )) ? $pObject->hasUserPermission( 'p_liberty_edit_all_status', FALSE ) : $gBitUser->hasPermission( 'p_liberty_edit_all_status' ); + } + + // default show content with status between 0 and 100; + $min_status_id = isset( $pParamHash['min_status_id'] ) && ( @BitBase::verifyId( $pParamHash['min_status_id'] ) || $pParamHash['min_status_id'] === 0 ) ? $pParamHash['min_status_id'] : 0; + $max_status_id = isset( $pParamHash['max_status_id'] ) && ( @BitBase::verifyId( $pParamHash['max_status_id'] ) || $pParamHash['max_status_id'] === 0 ) ? $pParamHash['max_status_id'] : 100; + // let owner see any of their own content with a status > -100 + $min_owner_status_id = isset( $pParamHash['min_owner_status_id'] ) && ( @BitBase::verifyId( $pParamHash['min_owner_status_id'] ) || $pParamHash['min_owner_status_id'] === 0 ) ? $pParamHash['min_owner_status_id'] : -100; + + if( $gBitSystem->isFeatureActive('liberty_display_status') && !$hasPerm ) { + if(( is_object( $pObject ) && !empty( $pObject->mType['content_type_guid'] ) && $pObject->mType['content_type_guid'] == 'bitcomment' ) + || ( !empty( $pParamHash['include_comments'] ) && $pParamHash['include_comments'] == 'y' )) { + // if we are getting a list of comments then lets check the owner of the comment root and the owner of the content + $ret['join_sql'] = " + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_comments` lcoms ON (lc.`content_id` = lcoms.`content_id`) + LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content` rlcs ON( rlcs.`content_id`=lcoms.`root_id` )"; + $ret['where_sql'] = + " AND lc.`content_status_id` < ".$max_status_id. + " AND ( + ( (rlcs.`user_id` = '".$gBitUser->getUserId()."' OR lc.`user_id` = '".$gBitUser->getUserId()."') AND lc.`content_status_id` > ".$min_owner_status_id.") + OR lc.`content_status_id` > ".$min_status_id." + )"; + } else { + $ret['where_sql'] = + " AND lc.`content_status_id` < ".$max_status_id. + " AND ( + (lc.`user_id` = '".$gBitUser->getUserId()."' AND lc.`content_status_id` > ".$min_owner_status_id.") + OR lc.`content_status_id` > ".$min_status_id." + )"; + } + } + + return $ret; +} + +/** + * liberty_content_preview + * + * @param array $pObject + * @access public + * @return void + */ +function liberty_content_preview( &$pObject ) { + global $gBitSystem, $gBitUser; + if( $gBitSystem->isFeatureActive( 'liberty_display_status' ) + && ( $gBitUser->hasPermission( 'p_liberty_edit_content_status' ) || $gBitUser->hasPermission( 'p_libert_edit_all_status' )) + && @BitBase::verifyId( $_REQUEST['content_status_id'] )) { + $pObject->mInfo['content_status_id'] = $_REQUEST['content_status_id']; + } + if( $gBitSystem->isFeatureActive( 'liberty_allow_change_owner' ) + && $gBitUser->hasPermission( 'p_liberty_edit_content_owner' ) + && @BitBase::verifyId( $_REQUEST['owner_id'] )) { + $pObject->mInfo['owner_id'] = $_REQUEST['owner_id']; + } + include_once( LIBERTY_PKG_INCLUDE_PATH.'edit_help_inc.php' ); +} + +/** + * liberty_content_display + * + * @param array $pObject + * @param array $pParamHash + * @access public + * @return void + */ +function liberty_content_display( &$pObject, &$pParamHash ) { + if( $pObject->isValid() ) { + global $gBitUser, $gBitSystem; + + // make sure user has appropriate permissions to view this content + if( !empty( $pParamHash['perm_name'] )) { + $pObject->verifyViewPermission(); + } + } +} + +/** + * liberty_content_edit + * + * @param array $pObject + * @param array $pParamHash + * @access public + * @return void + */ +function liberty_content_edit( &$pObject ) { + include_once( LIBERTY_PKG_INCLUDE_PATH.'edit_help_inc.php' ); + include_once( LIBERTY_PKG_INCLUDE_PATH.'edit_storage_inc.php' ); +} + + +// ================== Liberty File Processing Functions ================== + +/** + * Process uploaded files. Will automagically generate thumbnails for images + * + * @param array $pFileHash Data require to process the files + * @param array $pFileHash['name'] (required) Name of the uploaded file + * @param array $pFileHash['type'] (required) Mime type of the file uploaded + * @param array $pFileHash['dest_branch'] (required) Relative path where you want to store the file (trailing slash required) + * @param array $pFileHash['tmp_name'] (required) Absolute path to file including file name + * @param boolean $pFileHash['thumbnail'] (optional) Set to FALSE if you don't want to generate thumbnails + * @param array $pFileHash['thumbnail_sizes'] (optional) Decide what sizes thumbnails you want to create: icon, avatar, small, medium, large + * @param boolean $pMoveFile (optional) specify if you want to move or copy the original file + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ +function liberty_process_upload( &$pFileHash, $pMoveFile = TRUE ) { + global $gBitSystem; + + // Check for evil file extensions that could be execed on the server + if( preg_match( EVIL_EXTENSION_PATTERN, $pFileHash['name'] )) { + $pFileHash['type'] = 'text/plain'; + $pFileHash['name'] = $pFileHash['name'].'.txt'; + } + + if ( !is_windows() ) { + list( $pFileHash['name'], $pFileHash['type'] ) = $gBitSystem->verifyFileExtension( $pFileHash['tmp_name'], $pFileHash['name'] ); + } else { + //$pFile['type'] = $gBitSystem->verifyMimeType( $pFile['tmp_name'] ); + } + + $ext = strrpos( $pFileHash['name'], '.' ); + + // clean out crap that can make life difficult in server maintenance + $cleanedBaseName = preg_replace( '/[&\%:\/\\\]/', '', substr( $pFileHash['name'], 0, $ext ) ); + $pFileHash['dest_base_name'] = $cleanedBaseName; + $pFileHash['source_file'] = $pFileHash['tmp_name']; + // lowercase all file extensions + + $pFileHash['name'] = $cleanedBaseName.strtolower( substr( $pFileHash['name'], $ext ) ); + + // Thumbs.db is a windows My Photos/ folder file, and seems to really piss off imagick + $canThumbFunc = liberty_get_function( 'can_thumbnail' ); + if( !empty( $canThumbFunc ) && $canThumbFunc( $pFileHash['type'] ) && $pFileHash['name'] != 'Thumbs.db' ) { + $ret = liberty_process_image( $pFileHash, $pMoveFile ); + } else { + $ret = liberty_process_generic( $pFileHash, $pMoveFile ); + } + + return $ret; +} + +/** + * liberty_process_archive + * + * @param array $pFileHash + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ +function liberty_process_archive( &$pFileHash ) { + // sanity check: make sure tmp_name isn't empty. will scan / if it is + if( !is_array( $pFileHash ) || empty( $pFileHash['tmp_name'] ) || empty( $pFileHash['name'] ) ) { + return FALSE; + } + + $cwd = getcwd(); + // if the file has been uploaded using a form, we'll process the uploaded + // file directly. if it's been ftp uploaded or some other method used, + // we'll copy the file. in the case of xuploaded files, the files have been + // processed but don't have to be copied + if( empty( $pFileHash['preprocessed'] ) && !is_uploaded_file( $pFileHash['tmp_name'] ) && is_file( $pFileHash['tmp_name'] ) ) { + $tmpDir = get_temp_dir(); + $copyFile = tempnam( !empty( $tmpDir ) ? $tmpDir : '/tmp', $pFileHash['name'] ); + copy( $pFileHash['tmp_name'], $copyFile ); + $pFileHash['tmp_name'] = $copyFile; + } + + $dir = dirname( $pFileHash['tmp_name'] ); + $upExt = strtolower( substr( $pFileHash['name'], ( strrpos( $pFileHash['name'], '.' ) + 1 ) ) ); + $baseDir = $dir.'/'; + if( is_file( $pFileHash['tmp_name'] ) ) { + global $gBitUser; + $baseDir .= $gBitUser->mUserId; + } + + $destDir = $baseDir.'/'.basename( $pFileHash['tmp_name'] ); + // this if is very important logic back so subdirs get processed properly + if( ( is_dir( $baseDir ) || mkdir( $baseDir ) ) && @mkdir( $destDir ) ) { + // Some commands don't nicely support extracting to other directories + chdir( $destDir ); + list( $mimeType, $mimeExt ) = explode( '/', strtolower( $pFileHash['type'] ) ); + switch( $mimeExt ) { + case 'x-rar-compressed': + case 'x-rar': + $shellResult = shell_exec( "unrar x \"{$pFileHash['tmp_name']}\" \"$destDir\"" ); + break; + case 'x-bzip2': + case 'bzip2': + case 'x-gzip': + case 'gzip': + case 'x-tgz': + case 'x-tar': + case 'tar': + switch( $upExt ) { + case 'gz': + case 'tgz': $compressFlag = '-z'; break; + case 'bz2': $compressFlag = '-j'; break; + default: $compressFlag = ''; break; + } + $shellResult = shell_exec( "tar -x $compressFlag -f \"{$pFileHash['tmp_name']}\" -C \"$destDir\"" ); + break; + case 'x-zip-compressed': + case 'x-zip': + case 'zip': + $shellResult = shell_exec( "unzip \"{$pFileHash['tmp_name']}\" -d \"$destDir\"" ); + break; + case 'x-stuffit': + case 'stuffit': + $shellResult = shell_exec( "unstuff -d=\"$destDir\" \"{$pFileHash['tmp_name']}\" " ); + break; + default: + if( $upExt == 'zip' ) { + $shellResult = shell_exec( "unzip \"{$pFileHash['tmp_name']}\" -d \"$destDir\"" ); + } elseif( $upExt == 'rar' ) { + $shellResult = shell_exec( "unrar x \"{$pFileHash['tmp_name']}\" \"$destDir\"" ); + } elseif( $upExt == 'sit' || $upExt == 'sitx' ) { + print( "unstuff -d=\"$destDir\" \"{$pFileHash['tmp_name']}\" " ); + $shellResult = shell_exec( "unstuff -d=\"$destDir\" \"{$pFileHash['tmp_name']}\" " ); + } else { + $destDir = NULL; + } + break; + } + } + + chdir( $cwd ); + + // if we created a copy of the original, we remove it + if( !empty( $copyFile ) ) { + @unlink( $copyFile ); + } + + if( preg_match( "!^/+$!", $destDir )) { + // obviously something went horribly wrong + return FALSE; + } else { + return $destDir; + } +} + +/** + * liberty_process_generic + * + * @param array $pFileHash + * @param array $pMoveFile + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ +function liberty_process_generic( &$pFileHash, $pMoveFile = TRUE ) { + global $gBitSystem; + $ret = NULL; + if( !empty( $pFileHash['dest_file'] ) ) { + $destFile = $pFileHash['dest_file']; + } else { + if( $gBitSystem->isFeatureActive( 'liberty_originalize_file_names' ) ) { + $destFile = STORAGE_PKG_PATH.$pFileHash['dest_branch'].liberty_mime_get_default_file_name( $pFileHash['name'], $pFileHash['type'] ); + } else { + $destFile = STORAGE_PKG_PATH.$pFileHash['dest_branch'].$pFileHash['name']; + } + if ( is_windows() ) { + $destFile = str_replace( '//', '\\', str_replace( "\\", '\\', $destFile ) ); + } + } + + mkdir_p( dirname( $destFile ) ); + + if( is_file( $pFileHash['source_file']) ) { + if( $pFileHash['source_file'] == $destFile ) { + // do nothing if source and dest are the same + } elseif( $pMoveFile ) { + if( is_uploaded_file( $pFileHash['source_file'] ) ) { + move_uploaded_file( $pFileHash['source_file'], $destFile ); + } else { + rename( $pFileHash['source_file'], $destFile ); + } + } else { + copy( $pFileHash['source_file'], $destFile ); + } + $ret = $destFile; + } + $pFileHash['size'] = filesize( $destFile ); + + return $ret; +} + + +/** + * liberty_process_image + * + * @param array $pFileHash + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ +function liberty_process_image( &$pFileHash, $pMoveFile = TRUE ) { + global $gBitSystem; + $ret = NULL; + + list($type, $ext) = explode( '/', strtolower( $pFileHash['type'] ) ); + if( $resizePath = liberty_process_generic( $pFileHash, $pMoveFile )) { + $pFileHash['source_file'] = $resizePath; + + //set permissions if possible - necessary for some wonky shared hosting environments + if(chmod($pFileHash['source_file'], 0644)){ + //does nothing, but fails elegantly + } + $nameHold = $pFileHash['name']; + $sizeHold = $pFileHash['size']; + $ret = $pFileHash['source_file']; + + // do not thumbnail only if intentionally set to FALSE + if( !isset( $pFileHash['thumbnail'] ) || $pFileHash['thumbnail']==TRUE ) { + liberty_generate_thumbnails( $pFileHash ); + } + $pFileHash['name'] = $nameHold; + $pFileHash['size'] = $sizeHold; + } + return $ret; +} + +/** + * liberty_clear_thumbnails will clear all thummbnails found in a given directory + * + * @param array $pFileHash['dest_branch'] should contain the path to the dir where we should remove thumbnails + * @access public + * @return TRUE on success, FALSE on failure + */ +function liberty_clear_thumbnails( &$pFileHash ) { + if( !empty( $pFileHash['dest_branch'] )) { + $thumbHash = array( + 'source_file' => $pFileHash['dest_branch'], + 'mime_image' => FALSE, + ); + + // get thumbnails we want to remove + if( $thumbs = liberty_fetch_thumbnails( $thumbHash )) { + foreach( $thumbs as $thumb ) { + $thumb = BIT_ROOT_PATH.$thumb; + if( is_writable( $thumb )) { + unlink( $thumb ); + } + } + // if this was the thumbs subdirectory, we'll remove it if it's empty + if( basename( dirname( $thumb )) == 'thumbs' ) { + @rmdir( dirname( $thumb )); + } + } + } + return TRUE; +} + +/** + * liberty_get_function + * + * @param array $pType + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ +function liberty_get_function( $pType ) { + global $gBitSystem; + $processor = $gBitSystem->getConfig( 'image_processor', 'gd' ); + $ret = 'liberty_'.$processor.'_'.$pType.'_image'; + return( function_exists( $ret ) ? $ret : FALSE ); +} + +/** + * liberty_generate_thumbnails + * + * @param array $pFileHash + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ +function liberty_generate_thumbnails( $pFileHash ) { + global $gBitSystem, $gThumbSizes; + $ret = FALSE; + + if( $resizeFunc = liberty_get_function( 'resize' ) ) { + + // allow custom selection of thumbnail sizes + if( empty( $pFileHash['thumbnail_sizes'] )) { + if( !empty( $gThumbSizes ) && is_array( $gThumbSizes )) { + $pFileHash['thumbnail_sizes'] = array_keys( $gThumbSizes ); + } else { + $pFileHash['thumbnail_sizes'] = array( 'large', 'medium', 'small', 'avatar', 'icon' ); + } + } + + if( ( !preg_match( '#image/(gif|jpe?g|png)#i', $pFileHash['type'] ) && $gBitSystem->isFeatureActive( 'liberty_jpeg_originals' )) || in_array( 'original', $pFileHash['thumbnail_sizes'] ) ) { + // jpeg version of original + if( preg_match( '/pdf/i', $pFileHash['type'] ) ) { + // has a customer pdf rasterization function been defined? + if( function_exists( 'liberty_rasterize_pdf' ) && $rasteredFile = liberty_rasterize_pdf( $pFileHash['source_file'] ) ) { + $pFileHash['source_file'] = $rasteredFile; + } else { + $magickWand = NewMagickWand(); + if( !$pFileHash['error'] = liberty_magickwand_check_error( MagickReadImage( $magickWand, $pFileHash['source_file'] ), $magickWand )) { + MagickSetFormat( $magickWand, 'JPG' ); + if( MagickGetImageColorspace( $magickWand ) == MW_CMYKColorspace ) { + MagickProfileImage( $magickWand,"ICC", UTIL_PKG_PATH.'icc/srgb.icm' ); + MagickSetImageColorspace( $magickWand, MW_sRGBColorspace ); + } + + $imgWidth = MagickGetImageWidth( $magickWand ); + $imgHeight = MagickGetImageHeight( $magickWand ); + + MagickSetImageUnits( $magickWand, MW_PixelsPerInchResolution ); + MagickSetResolution( $magickWand, 300, 300 ); + $rasteredFile = dirname( $pFileHash['source_file'] ).'/original.jpg'; + if( !$pFileHash['error'] = liberty_magickwand_check_error( MagickWriteImage( $magickWand, $rasteredFile ), $magickWand )) { + $pFileHash['source_file'] = $rasteredFile; + } + } + } + } else { + $pFileHash['dest_base_name'] = 'original'; + $pFileHash['name'] = 'original.jpg'; + $pFileHash['max_width'] = MAX_THUMBNAIL_DIMENSION; + $pFileHash['max_height'] = MAX_THUMBNAIL_DIMENSION; + if( $convertedFile = $resizeFunc( $pFileHash )) { + $pFileHash['source_file'] = $convertedFile; + $ret = TRUE; + } + } + $pFileHash['type'] = $gBitSystem->verifyMimeType( $pFileHash['source_file'] ); + } + + // override $mimeExt if we have a custom setting for it + if( $gBitSystem->isFeatureActive( 'liberty_thumbnail_format' )) { + $mimeExt = $gBitSystem->getConfig( 'liberty_thumbnail_format' ); + } else { + list( $type, $mimeExt ) = preg_split( '#/#', strtolower( $pFileHash['type'] )); + } + + if( preg_match( "!(png|gif)!", $mimeExt )) { + $destExt = '.'.$mimeExt; + } else { + $destExt = '.jpg'; + } + + $initialDestPath = $pFileHash['dest_branch']; + foreach( $pFileHash['thumbnail_sizes'] as $thumbSize ) { + if( isset( $gThumbSizes[$thumbSize] )) { + $pFileHash['dest_base_name'] = $thumbSize; + $pFileHash['name'] = $thumbSize.$destExt; + if( !empty( $gThumbSizes[$thumbSize]['width'] )) { + $pFileHash['max_width'] = $gThumbSizes[$thumbSize]['width']; + } else { + // Have to unset since we reuse $pFileHash + unset( $pFileHash['max_width'] ); + } + + // reset dest_branch for created thumbs + if( !empty( $pFileHash['thumb_path'] ) ) { + $pFileHash['dest_file'] = $pFileHash['thumb_path'].$pFileHash['name']; + } else { + // create a subdirectory for the thumbs + $pFileHash['dest_branch'] = $initialDestPath.'thumbs/'; + clearstatcache(); + if( !is_dir( STORAGE_PKG_PATH.$pFileHash['dest_branch'] )) { + @mkdir( STORAGE_PKG_PATH.$pFileHash['dest_branch'], 0775, TRUE ); + clearstatcache(); + } + } + + if( !empty( $gThumbSizes[$thumbSize]['height'] )) { + $pFileHash['max_height'] = $gThumbSizes[$thumbSize]['height']; + } else { + // Have to unset since we reuse $pFileHash + unset( $pFileHash['max_height'] ); + } + if( $pFileHash['icon_thumb_path'] = $resizeFunc( $pFileHash )) { + $ret = TRUE; + // use the previous thumb as the source for the next, decreasingly smaller thumb as this GREATLY increases speed + $pFileHash['source_file'] = $pFileHash['icon_thumb_path']; + } + } + } + + // to keep everything in bitweaver working smoothly, we need to remove the thumbs/ subdir again + $pFileHash['dest_branch'] = $initialDestPath; + } + + return $ret; +} + +/** + * fetch all available thumbnails for a given item. if no thumbnails are present, get thumbnailing image or the appropriate mime type icon + * + * @param array $pParamHash Hash of all settings used to fetch thumbnails, including source_file, default_image, thumbnail_sizes, and mime_image + * @access public + * @return array of available thumbnails or mime icons + * TODO: individual options are only for legacy reasons - remove options and deprecated() soon - xing - Monday Jun 23, 2008 22:36:53 CEST + */ +function liberty_fetch_thumbnails( $pParamHash ) { + global $gBitSystem, $gThumbSizes; + $ret = array(); + + if( !empty( $pParamHash['source_file'] )) { + if( empty( $pParamHash['thumbnail_sizes'] )) { + $pParamHash['thumbnail_sizes'] = array_keys( $gThumbSizes ); + } + + // liberty file processors automatically pick the best format for us. we can force a format though. + // using array_unique will give us the best order in which to look for the thumbnails + $exts = array_unique( array( $gBitSystem->getConfig( 'liberty_thumbnail_format', 'jpg' ), 'jpg', 'png', 'gif', 'x-jpeg' )); + + // short hand + $path = &$pParamHash['source_file']; + // $path might already be the absolute path or it might already contain BIT_ROOT_URL + if( !( $path = preg_replace( "!^".preg_quote( STORAGE_PKG_PATH, "!" )."!", "", $path ))) { + $path = preg_replace( "!^".preg_quote( STORAGE_PKG_URL, "!" )."!", "", $path ); + } + + // remove the filename if there is one (we can't just use dirname() becuase we might only have the path to the dir) + $dir = substr( $path, 0, strrpos( $path, '/' ) + 1 ); + // assume thumb sizes are from largest to smallest. reverse so smaller can be used if larger don't exist + $lastSize = NULL; + foreach( array_reverse( $pParamHash['thumbnail_sizes'] ) as $size ) { + foreach( $exts as $ext ) { + $image = $size.'.'.$ext; + $thumbDir = is_dir( STORAGE_PKG_PATH.$dir.'thumbs/' ) ? $dir.'thumbs/' : $dir; + if( is_readable( STORAGE_PKG_PATH.$thumbDir.$image )) { + $ret[$size] = (empty( $_REQUEST['uri_mode'] ) ? STORAGE_PKG_URL : STORAGE_PKG_URI).$thumbDir.$image; + } + } + // fetch mime image unless we set this to FALSE + if(( !isset( $pParamHash['mime_image'] ) || $pParamHash['mime_image'] === TRUE ) && empty( $ret[$size] )) { + if( $lastSize && !empty( $ret[$lastSize] ) ) { + $ret[$size] = $ret[$lastSize]; + } + } + $lastSize = $size; + } + + // default if nothing else is available + foreach( array_reverse( $pParamHash['thumbnail_sizes'] ) as $size ) { + if( empty( $ret[$size] ) ) { + if( !empty( $pParamHash['default_image'] )) { + $ret[$size] = $pParamHash['default_image']; + } else { + // we need to make sure we have an image name that we can look up the mime type + $path .= ( strrpos( $dir, '/' ) == strlen( $path ) ? 'dummy.jpg' : basename( $path )); + $ret[$size] = LibertySystem::getMimeThumbnailURL( $gBitSystem->lookupMimeType( $path ), substr( $path, strrpos( $path, '.' ) + 1 )); + } + } + } + } + + return $ret; +} + +/** + * fetch a single available thumbnail for a given item. if no thumbnail is present, return NULL + * + * @param array $pParamHash Hash of all settings used to fetch thumbnails including: size, source_file, default_image, and mime_image + * @access public + * @return string url + * TODO: individual options are only for legacy reasons - remove options and deprecated() soon - xing - Monday Jun 23, 2008 22:36:53 CEST + */ +function liberty_fetch_thumbnail_url( $pParamHash ) { + if( !empty( $pParamHash['source_file'] )) { + if( empty( $pParamHash['size'] )) { + $pParamHash['size'] = 'small'; + } + + $pParamHash['thumbnail_sizes'] = array( $pParamHash['size'] ); + $ret = liberty_fetch_thumbnails( $pParamHash ); + + return( !empty( $ret[$pParamHash['size']] ) ? $ret[$pParamHash['size']] : NULL ); + } +} + +/** + * get a set of image size options based on $gThumbSizes + * + * @param string $pEmptyOption string to use as empty option - if set to FALSE no empty string is eincluded - Note that string is tra()'d + * @access public + * @return array of image size options suitable for use in a form + */ +function get_image_size_options( $pEmptyOption = 'Disable this feature' ) { + global $gThumbSizes; + $ret = array(); + if( !empty( $pEmptyOption )) { + $ret[''] = tra( $pEmptyOption ); + } + foreach( $gThumbSizes as $key => $size ) { + $ret[$key] = tra( ucfirst( $key ))." ( ". ( empty( $size['width'] ) ? tra( 'unlimited' ) : $size['width'] ) ." x ". ( empty($size['height'] ) ? tra('unlimited') : $size['height'] ) ." ".tra( 'pixels' )." )"; + } + return $ret; +} + +/** + * get_leadtitle will fetch the string before the liberty_subtitle_delimiter + * + * @param string $pString string that should be checked for the delimiter + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ +function get_leadtitle( $pString ) { + global $gBitSystem; + return( substr( $pString, 0, strpos( $pString, $gBitSystem->getConfig( 'liberty_subtitle_delimiter', ':' )))); +} + +/** + * get_subtitle will fetch the string after the liberty_subtitle_delimiter + * + * @param string $pString string that should be checked for the delimiter + * @access public + * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure + */ +function get_subtitle( $pString ) { + global $gBitSystem; + if(( $start = strpos( $pString, $gBitSystem->getConfig( 'liberty_subtitle_delimiter', ':' ))) !== FALSE ) { + return( substr( $pString, ( $start + 1 ))); + } +} +?> diff --git a/includes/lookup_content_inc.php b/includes/lookup_content_inc.php new file mode 100644 index 0000000..d9ebaa6 --- /dev/null +++ b/includes/lookup_content_inc.php @@ -0,0 +1,40 @@ +<?php +/** + * lookup_content_inc + * + * @author spider <spider@steelsun.com> + * @version $Revision$ + * @package liberty + * @subpackage functions + */ + global $gContent; + + if( @BitBase::verifyId( $_REQUEST['structure_id'] ) ) { + /** + * required setup + */ + require_once( LIBERTY_PKG_CLASS_PATH.'LibertyStructure.php'); + $_REQUEST['structure_id'] = preg_replace( '/[\D]/', '', $_REQUEST['structure_id'] ); + $gStructure = new LibertyStructure( $_REQUEST['structure_id'] ); + if( $gStructure->load() ) { + $gStructure->loadNavigation(); + $gStructure->loadPath(); + $gBitSmarty->assign( 'structureInfo', $gStructure->mInfo ); + // $_REQUEST['page_id'] = $gStructure->mInfo['page_id']; + if( $viewContent = LibertyBase::getLibertyObject( $gStructure->mInfo['content_id'], $gStructure->mInfo['content_type']['content_type_guid'] ) ) { + $viewContent->setStructure( $_REQUEST['structure_id'] ); + $gBitSmarty->assignByRef( 'pageInfo', $viewContent->mInfo ); + $gContent = &$viewContent; + $gBitSmarty->assignByRef( 'gContent', $gContent ); + } + } + } elseif( @BitBase::verifyId( $_REQUEST['content_id'] ) ) { + $_REQUEST['content_id'] = preg_replace( '/[\D]/', '', $_REQUEST['content_id'] ); + require_once( LIBERTY_PKG_CLASS_PATH.'LibertyBase.php'); + if( $gContent = LibertyBase::getLibertyObject( $_REQUEST['content_id'] ) ) { + $gBitSmarty->assignByRef( 'gContent', $gContent ); + $gBitSmarty->assignByRef( 'pageInfo', $gContent->mInfo ); + } + } + +?> |
