summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlsces <lester@lsces.co.uk>2025-08-24 14:41:07 +0100
committerlsces <lester@lsces.co.uk>2025-08-24 14:41:07 +0100
commita6cc0f5e5cf42c9a3a5b35a9d02cc25977a5896a (patch)
treefa8809e0f3f42a36caa57d31267443e42ce81b6e
parent4b845764a11aa03a57a08e82db78f765f0606782 (diff)
downloadprotector-a6cc0f5e5cf42c9a3a5b35a9d02cc25977a5896a.tar.gz
protector-a6cc0f5e5cf42c9a3a5b35a9d02cc25977a5896a.tar.bz2
protector-a6cc0f5e5cf42c9a3a5b35a9d02cc25977a5896a.zip
Initial population in github
-rwxr-xr-x[-rw-r--r--]README.md10
-rwxr-xr-xadmin/schema_inc.php51
-rwxr-xr-xicons/pkg_protector.gifbin0 -> 1772 bytes
-rwxr-xr-xicons/pkg_protector.pngbin0 -> 3697 bytes
-rwxr-xr-xincludes/bit_setup_inc.php34
-rwxr-xr-xincludes/classes/LibertyProtector.php233
-rwxr-xr-xliberty_plugins/mime.flatdefault.php462
-rwxr-xr-xtemplates/choose_protection.tpl18
-rwxr-xr-xtemplates/protector_service_icon.tpl16
9 files changed, 823 insertions, 1 deletions
diff --git a/README.md b/README.md
index 1bf86e0..bc14ed7 100644..100755
--- a/README.md
+++ b/README.md
@@ -1 +1,9 @@
-# protector \ No newline at end of file
+protector package adds an additional level of content management of that of simple package selection.
+The user role (formally 'group') is taken as this level and only users assigned to the selected
+role will be able to see the content in lists and view it.
+
+An additional facility added to protector for central management of attachments although this is not
+required for protector to work.
+mime.flatdefault.php uses /storage/attachments/<content_id%1000./<content_id> for all content, rather
+than filing them under each user. This is appropriate for sites where multiple users all add content
+to a central set of documentation. \ No newline at end of file
diff --git a/admin/schema_inc.php b/admin/schema_inc.php
new file mode 100755
index 0000000..206e98a
--- /dev/null
+++ b/admin/schema_inc.php
@@ -0,0 +1,51 @@
+<?php
+
+$tables = [
+
+'liberty_content_role_map' => "
+ content_id I4 PRIMARY,
+ role_id I4 PRIMARY
+ CONSTRAINT ', CONSTRAINT `protector_role_ref` FOREIGN KEY (`role_id`) REFERENCES `".BIT_DB_PREFIX."users_roles` (`role_id`)
+ , CONSTRAINT `protector_content_ref` FOREIGN KEY (`content_id`) REFERENCES `".BIT_DB_PREFIX."liberty_content` (`content_id`)'
+",
+
+];
+
+global $gBitInstaller;
+
+foreach( array_keys( $tables ) AS $tableName ) {
+ $gBitInstaller->registerSchemaTable( PROTECTOR_PKG_NAME, $tableName, $tables[$tableName] );
+}
+
+$gBitInstaller->registerPackageInfo( PROTECTOR_PKG_NAME, [
+ 'description' => "Protector restricts access to content based on user roles.",
+ 'license' => '<a href="http://www.gnu.org/licenses/licenses.html#LGPL">LGPL</a>',
+] );
+
+// ### Indexes
+//$indices = array (
+//);
+//$gBitInstaller->registerSchemaIndexes( PROTECTOR_PKG_NAME, $indices );
+
+// ### Sequences
+//$sequences = [
+// 'protector_security_id_seq' => [ 'start' => 1 ]
+//];
+//$gBitInstaller->registerSchemaSequences( PROTECTOR_PKG_NAME, $sequences );
+
+// ### Default UserPermissions
+$gBitInstaller->registerUserPermissions( PROTECTOR_PKG_NAME, [
+ [ 'bit_p_create_protector', 'Can create a protector', 'registered', PROTECTOR_PKG_NAME ],
+ [ 'bit_p_protector_edit', 'Can edit any protector', 'editors', PROTECTOR_PKG_NAME ],
+ [ 'bit_p_protector_admin', 'Can admin protector', 'editors', PROTECTOR_PKG_NAME ],
+ [ 'bit_p_read_protector', 'Can read protector', 'basic', PROTECTOR_PKG_NAME ],
+] );
+
+// ### Default Preferences
+$gBitInstaller->registerPreferences( PROTECTOR_PKG_NAME, [
+ [ PROTECTOR_PKG_NAME, 'protector_default_ordering','title_desc' ],
+ [ PROTECTOR_PKG_NAME, 'protector_list_content_id','y' ],
+ [ PROTECTOR_PKG_NAME, 'protector_list_title','y' ],
+ [ PROTECTOR_PKG_NAME, 'protector_list_description','y' ],
+ [ PROTECTOR_PKG_NAME, 'protector_single_role','y' ],
+] );
diff --git a/icons/pkg_protector.gif b/icons/pkg_protector.gif
new file mode 100755
index 0000000..0a5c9a8
--- /dev/null
+++ b/icons/pkg_protector.gif
Binary files differ
diff --git a/icons/pkg_protector.png b/icons/pkg_protector.png
new file mode 100755
index 0000000..deea728
--- /dev/null
+++ b/icons/pkg_protector.png
Binary files differ
diff --git a/includes/bit_setup_inc.php b/includes/bit_setup_inc.php
new file mode 100755
index 0000000..e89cb8d
--- /dev/null
+++ b/includes/bit_setup_inc.php
@@ -0,0 +1,34 @@
+<?php
+global $gBitSystem, $gBitSmarty;
+
+$pRegisterHash = [
+ 'package_name' => 'protector',
+ 'package_path' => dirname( dirname( __FILE__ ) ).'/',
+ 'service' => LIBERTY_SERVICE_ACCESS_CONTROL,
+];
+
+// fix to quieten down VS Code which can't see the dynamic creation of these ...
+define( 'PROTECTOR_PKG_NAME', $pRegisterHash['package_name'] );
+define( 'PROTECTOR_PKG_URL', BIT_ROOT_URL . basename( $pRegisterHash['package_path'] ) . '/' );
+define( 'PROTECTOR_PKG_PATH', BIT_ROOT_PATH . basename( $pRegisterHash['package_path'] ) . '/' );
+define( 'PROTECTOR_PKG_INCLUDE_PATH', BIT_ROOT_PATH . basename( $pRegisterHash['package_path'] ) . '/includes/');
+define( 'PROTECTOR_PKG_CLASS_PATH', BIT_ROOT_PATH . basename( $pRegisterHash['package_path'] ) . '/includes/classes/');
+define( 'PROTECTOR_PKG_ADMIN_PATH', BIT_ROOT_PATH . basename( $pRegisterHash['package_path'] ) . '/admin/');
+
+$gBitSystem->registerPackage( $pRegisterHash );
+
+if( $gBitSystem->isPackageActive( 'protector' ) ) {
+
+ $gLibertySystem->registerService( LIBERTY_SERVICE_ACCESS_CONTROL, PROTECTOR_PKG_NAME, [
+ 'content_display_function' => 'protector_content_display',
+ 'content_preview_function' => 'protector_content_edit',
+ 'content_edit_function' => 'protector_content_edit',
+ 'content_store_function' => 'protector_content_store',
+ 'comment_store_function' => 'protector_comment_store',
+ 'content_expunge_function' => 'protector_content_expunge',
+ 'content_list_sql_function' => 'protector_content_list',
+ 'content_load_sql_function' => 'protector_content_load',
+ 'content_edit_mini_tpl' => 'bitpackage:protector/choose_protection.tpl',
+ 'content_icon_tpl' => 'bitpackage:protector/protector_service_icon.tpl',
+ ] );
+}
diff --git a/includes/classes/LibertyProtector.php b/includes/classes/LibertyProtector.php
new file mode 100755
index 0000000..5545072
--- /dev/null
+++ b/includes/classes/LibertyProtector.php
@@ -0,0 +1,233 @@
+<?php
+/**
+ * protector package limits content based on user role
+ *
+ * @copyright (c) 2004-15 bitweaver.org
+ * @package protector
+ */
+
+/**
+ * required setup
+ */
+namespace Bitweaver\Liberty;
+
+/**
+ * Protector class to illustrate best practices when creating a new bitweaver package that
+ * builds on core bitweaver functionality, such as the Liberty CMS engine
+ *
+ * @package protector
+ */
+class LibertyProtector extends LibertyBase {
+
+ /**
+ * During initialisation, be sure to call our base constructors
+ **/
+ function __construct( $pContentId=0 ) {
+ $this->mContentId = $pContentId ;
+ parent::__construct();
+ }
+
+ /**
+ * Update the liberty_content_role_map table with corrected role_id(s).
+ *
+ * In -1 for anonymouse is not stored, switching content to anonymouse will clear array
+ *
+ * @param object $pParamHash
+ */
+ function storeProtection( &$pParamHash ) {
+ global $gBitSystem;
+ if( \Bitweaver\BitBase::verifyId( $pParamHash['protector']['role_id'] ?? 0 ) ) {
+ $this->mDb->query( "DELETE FROM `".BIT_DB_PREFIX."liberty_content_role_map` WHERE `content_id`=?", array( $pParamHash['content_id'] ) );
+ if( $gBitSystem->isFeatureActive( 'protector_single_role' ) ) {
+ if( $pParamHash['protector']['role_id'] != -1 )
+ $this->mDb->query( "INSERT INTO `".BIT_DB_PREFIX."liberty_content_role_map` ( `role_id`, `content_id` ) VALUES ( ?, ? )", array( $pParamHash['protector']['role_id'], $pParamHash['content_id'] ) );
+ } else {
+ foreach( $pParamHash['protector']['role_id'] AS $roleId ) {
+ if( $roleId != -1 )
+ $this->mDb->query( "INSERT INTO `".BIT_DB_PREFIX."liberty_content_role_map` ( `role_id`, `content_id` ) VALUES ( ?, ? )", array( $roleId, $pParamHash['content_id'] ) );
+ }
+ }
+ }
+ return count( $this->mErrors ) == 0;
+ }
+
+ /**
+ * Delete entry(ies) from liberty_content_role_map table with content_id.
+ *
+ * @param object $pContent
+ */
+ public function expunge(): bool {
+ if( \Bitweaver\BitBase::verifyId( $this->mContentId ) ) {
+ $this->mDb->query( "DELETE FROM `".BIT_DB_PREFIX."liberty_content_role_map` WHERE `content_id`=?", array( $this->mContentId ) );
+ }
+ return true;
+ }
+
+ /**
+ * @return array liberty_content_role_map for selected content_id
+ * Ret -1 for anonymouse if alternatives are not stored
+ **/
+ public function getProtectionList( $ContentId=null ) {
+ global $gBitSystem;
+ $ret = array( '-1' <= $ContentId );
+ if( isset( $ContentId ) ) {
+ $ret = $this->mDb->GetAssoc( "SELECT `role_id`, `content_id` FROM `".BIT_DB_PREFIX."liberty_content_role_map` WHERE `content_id`=?", array( $ContentId ) );
+ }
+ return $ret;
+ }
+}
+
+/**
+* function to provide list of filtered content
+**/
+function protector_content_list() {
+ global $gBitUser;
+ $roles = array_keys($gBitUser->mRoles);
+ $ret = array(
+ 'join_sql' => " LEFT JOIN `".BIT_DB_PREFIX."liberty_content_role_map` lcrm ON ( lc.`content_id`=lcrm.`content_id` ) LEFT OUTER JOIN `".BIT_DB_PREFIX."users_roles_map` purm ON ( purm.`user_id`=".$gBitUser->mUserId." ) AND ( purm.`role_id`=lcrm.`role_id` ) ",
+ 'where_sql' => " AND (lcrm.`content_id` IS null OR lcrm.`role_id` IN(". implode(',', array_fill(0, count($roles), '?')) ." ) OR purm.`user_id`=?) ",
+ 'bind_vars' => array_merge( $roles, array( $gBitUser->mUserId ) ),
+ );
+ return $ret;
+}
+
+/**
+ * function to load a filtered content element
+ *
+ * @param object $pContent
+ */
+function protector_content_load( $pContent = null ) {
+ global $gBitUser;
+
+ $roles = array_keys($gBitUser->mRoles);
+ protector_content_verify_access( $pContent, $roles );
+ $ret = array(
+ 'join_sql' => " LEFT JOIN `".BIT_DB_PREFIX."liberty_content_role_map` lcrm ON ( lc.`content_id`=lcrm.`content_id` ) LEFT OUTER JOIN `".BIT_DB_PREFIX."users_roles_map` purm ON ( purm.`role_id`=lcrm.`role_id` ) ",
+ 'where_sql' => " AND (lcrm.`content_id` IS null OR lcrm.`role_id` IN(". implode(',', array_fill(0, count($roles), '?')) ." ) OR purm.`user_id`=?) ",
+ 'bind_vars' => array( $gBitUser->mUserId ),
+ );
+ $ret['bind_vars'] = array_merge( $roles, $ret['bind_vars'] );
+ return $ret;
+}
+
+/**
+* function to store a filtered content element
+*
+* @param object $pObject
+* @param array $pParamHash
+**/
+function protector_content_store( $pObject, $pParamHash ) {
+ global $gBitSystem, $gProtector;
+ $errors = null;
+ // If a content access system is active, let's call it
+ if( $gBitSystem->isPackageActive( 'protector' ) ) {
+ if( !$gProtector->storeProtection( $pParamHash ) ) {
+ $errors['protector'] = $gProtector->mErrors['security'];
+ }
+ }
+ return $errors;
+}
+
+/**
+* function to store a filtered comment element
+*
+* @param object $pContent
+* @param array $pParamHash
+**/
+function protector_comment_store( $pContent, $pParamHash ) {
+ global $gBitSystem, $gProtector;
+ $errors = null;
+ // If a content access system is active, let's call it
+ if( $gBitSystem->isPackageActive( 'protector' ) ) {
+ if( isset( $pParamHash['comments_parent_id'] ) ) {
+ $pParamHash['protector']['role_id'] = $pContent->mDb->GetOne( "SELECT `role_id` FROM `".BIT_DB_PREFIX."liberty_content_role_map` WHERE `content_id`=?", array( $pParamHash['comments_parent_id'] ) );
+ }
+ if( !$gProtector->storeProtection( $pParamHash ) ) {
+ $errors['protector'] = $gProtector->mErrors['security'];
+ }
+ }
+ return $errors;
+}
+
+/**
+* function to delete a filtered content element
+*
+* @param object $pContent
+* @param array $pParamHash
+**/
+function protector_content_expunge( $pContent = null ) {
+ if( \Bitweaver\BitBase::verifyId( $pContent->mContentId ) ) {
+ $pContent->mDb->query( "DELETE FROM `".BIT_DB_PREFIX."liberty_content_role_map` WHERE `content_id`=?", array( $pContent->mContentId ) );
+ }
+}
+
+/**
+* function to display a filtered content element
+*
+* @param object $pContent
+* @param array $pParamHash
+**/
+function protector_content_display( &$pContent, &$pParamHash ) {
+ global $gBitSystem, $gBitSmarty;
+ $pContent->hasUserPermission( $pParamHash['perm_name'] ?? '' );
+}
+
+/**
+* function to verify access to a filtered content element
+*
+* @param object $pContent
+* @param array $pHash
+**/
+function protector_content_verify_access( $pContent, $pHash ) {
+ global $gBitUser, $gBitSystem;
+
+ $error = null;
+ if ( $pContent && $pContent->isValid() ) {
+ if( !$pContent->verifyId( $pContent->mContentId ) ) {
+ }
+ if( $pContent->verifyId( $pContent->mContentId ) ) {
+ $query = "SELECT lc.`content_id`, lcrm.`role_id` as `is_protected`
+ FROM `".BIT_DB_PREFIX."liberty_content` lc
+ LEFT JOIN `".BIT_DB_PREFIX."liberty_content_role_map` lcrm ON ( lc.`content_id`=lcrm.`content_id` ) LEFT OUTER JOIN `".BIT_DB_PREFIX."users_roles_map` urm ON ( urm.`user_id`=".$gBitUser->mUserId." ) AND ( urm.`role_id`=lcrm.`role_id` )
+ WHERE lc.`content_id` = ?";
+ $ret = $pContent->mDb->getRow( $query, array( $pContent->mContentId ) );
+ if( $ret and is_numeric($ret['is_protected']) and !in_array( $ret['is_protected'], $pHash ) ) {
+ $gBitSystem->fatalPermission( 'protector permission fail' );
+ } else {
+ if ( $ret and is_numeric($ret['is_protected']) and $ret['is_protected'] == -1 )
+ $pContent->mViewPublic = 'public';
+ }
+ }
+ }
+ return $error;
+}
+
+/**
+* function to edit a filtered content element
+*
+* @param object $pContent
+**/
+function protector_content_edit( $pContent ) {
+ global $gProtector, $gBitUser, $gBitSmarty;
+ $roles = $gBitUser->getRoles();
+ $roles[-1]['role_name'] = "~~ System Default ~~";
+ ksort( $roles );
+ foreach( array_keys( $roles ) as $roleId ) {
+ $protectorRolesId[$roleId] = $roleId != -1 ? $roles[$roleId]['role_name'] : "~~ System Default ~~";
+ }
+ if ( $pContent->mContentId ) {
+ $serviceHash['protector']['role'] = $gProtector->getProtectionList( $pContent->mContentId );
+ } else {
+ if ( isset( $pContent->mInfo['parent_id'] ) ) {
+ $serviceHash['protector']['role'] = $gProtector->getProtectionList( $pContent->mInfo['parent_id'] );
+ }
+ }
+ if ( isset( $serviceHash['protector']['role'] ) ) { $prot = array_keys( $serviceHash['protector']['role'] ); }
+ $serviceHash['protector']['role_id'] = empty( $prot[0] ) ? -1 : $prot[0];
+ $gBitSmarty->assign( 'serviceHash', $serviceHash );
+ $gBitSmarty->assign( 'protectorRolesId', $protectorRolesId );
+ $gBitSmarty->assign( 'protectorRoles', $roles );
+}
+
+global $gProtector;
+$gProtector = new LibertyProtector();
diff --git a/liberty_plugins/mime.flatdefault.php b/liberty_plugins/mime.flatdefault.php
new file mode 100755
index 0000000..431a495
--- /dev/null
+++ b/liberty_plugins/mime.flatdefault.php
@@ -0,0 +1,462 @@
+<?php
+/**
+ * Mime handler - flat attachments model default
+ *
+ * As an alternative to storing file attachments in a user based storage tree, this version of mime.default.php
+ * provides for a flat filing system based on id. A two level tree is currently provided bassed on a Mod 1000 trimming
+ * of the id number for the first level directories. This limits each branch to a maximum of 1000 sub directories but
+ * a change the mime_default_branch setting will allow other trimmimg to be implemented.
+ *
+ * Include
+ * define( 'LIBERTY_DEFAULT_MIME_HANDLER', 'mimeflatdefault' );
+ * in config_inc.php to activate
+ *
+ * @package liberty
+ * @subpackage liberty_mime_handler
+ **/
+
+/**
+ * setup
+ */
+namespace Bitweaver\Liberty;
+use Bitweaver\BitBase;
+use Bitweaver\KernelTools;
+
+global $gLibertySystem;
+
+/**
+ * This is the name of the plugin - max char length is 16
+ * As a naming convention, the liberty mime handler definition should start with:
+ * PLUGIN_MIME_GUID_
+ */
+define( 'PLUGIN_MIME_GUID_FLATDEFAULT', 'mimeflatdefault' );
+define( 'FLAT_STORAGE_NAME', 'attachments' );
+
+$pluginParams = [
+ // Set of functions and what they are called in this paricular plugin
+ // Use the GUID as your namespace
+ 'verify_function' => 'mime_default_verify',
+ 'store_function' => 'mime_default_store',
+ 'update_function' => 'mime_default_update',
+ 'load_function' => 'mime_default_load',
+ 'branch_function' => 'mime_default_branch',
+ 'download_function' => 'mime_default_download',
+ 'expunge_function' => 'mime_default_expunge',
+ // Brief description of what the plugin does
+ 'title' => 'Default Flat File Handler',
+ 'description' => 'This mime handler can handle any file type, creates thumbnails when possible and will make the file available as an attachment under a flat file system.',
+ // Templates to display the files
+ 'upload_tpl' => 'bitpackage:liberty/mime/default/upload.tpl',
+ 'view_tpl' => 'bitpackage:liberty/mime/default/view.tpl',
+ 'inline_tpl' => 'bitpackage:liberty/mime/default/inline.tpl',
+ 'storage_tpl' => 'bitpackage:liberty/mime/default/storage.tpl',
+ 'attachment_tpl' => 'bitpackage:liberty/mime/default/attachment.tpl',
+ // This should be the same for all mime plugins
+ 'plugin_type' => MIME_PLUGIN,
+ // This needs to be specified by plugins that are included by other plugins
+ 'file_name' => 'mime.flatdefault.php',
+ // Ensure only one mime.default.php version is active and visible
+ // Set this to true if you want the plugin active right after installation
+ 'auto_activate' => false,
+ // Help page on bitweaver.org
+ //'help_page' => 'MimeHelpPage',
+
+ // Here you can use a perl regular expression to pick out file extensions you want to handle
+ // e.g.: Some image types: '#^image/(jpe?g|gif|png)#i'
+ // This plugin will be picked if nothing matches.
+ //'mimetypes' => [ '/.*/' ],
+];
+$gLibertySystem->registerPlugin( PLUGIN_MIME_GUID_FLATDEFAULT, $pluginParams );
+
+/**
+ * Sanitise and validate data before it's stored
+ *
+ * @param array $pStoreRow Hash of data that needs to be stored
+ * @param array $pStoreRow['upload'] Hash passed in by $_FILES upload
+ * @access public
+ * @return bool true on success, false on failure - $pStoreRow['errors'] will contain reason
+ */
+if( !function_exists( '\Bitweaver\Liberty\mime_default_verify' )) {
+ function mime_default_verify( &$pStoreRow ) {
+ global $gBitSystem, $gBitUser;
+ $ret = false;
+
+ // 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?
+ $pStoreRow['user_id'] = BitBase::verifyId( $gBitUser->mUserId ) ? $gBitUser->mUserId : ROOT_USER_ID;
+
+ // Bypass p_liberty_attach_attachments for managed uploads - access controlled by fisheye/treasury or other uploader
+ // Ignore warning when p_liberty_attach_attachments totally disabled
+ // if( $this->hasUserPermission( 'p_liberty_manage_attachments' )) {
+ $pStoreRow['no_perm_check'] = true;
+ // }
+
+ if( !empty( $pStoreRow['upload']['tmp_name'] ) && is_file( $pStoreRow['upload']['tmp_name'] )) {
+ // attachment_id is only set when we are updating the file
+ if( BitBase::verifyId( $pStoreRow['upload']['attachment_id'] ?? 0 )) {
+ // if a new file has been uploaded, we need to get some information from the database for the file update
+ $fileInfo = $gBitSystem->mDb->getRow( "
+ SELECT la.`attachment_id`, lf.`file_id`, lf.`file_name`
+ FROM `".BIT_DB_PREFIX."liberty_attachments` la
+ INNER JOIN `".BIT_DB_PREFIX."liberty_files` lf ON ( lf.`file_id` = la.`foreign_id` )
+ WHERE la.`attachment_id` = ?", [ $pStoreRow['upload']['attachment_id'] ] );
+ $pStoreRow = array_merge( $pStoreRow, $fileInfo );
+ } else {
+ // try to generate thumbnails for the upload
+ //$pStoreRow['upload']['thumbnail'] = !$gBitSystem->isFeatureActive( 'liberty_offline_thumbnailer' );
+ $pStoreRow['upload']['thumbnail'] = true;
+ // Little cluge - unattached files create an empty ['upload']['attachment_id'] attached ones do not
+ // Really need core liberty to be consistent on identifying these - ideally adding flag for linked files
+ $pStoreRow['attachment_id'] = isset($pStoreRow['upload']['attachment_id'])
+ ? $gBitSystem->mDb->GenID( 'liberty_content_id_seq' )
+ : $pStoreRow['content_id'];
+ }
+
+ // Generic values needed by the storing mechanism
+ $pStoreRow['upload']['source_file'] = $pStoreRow['upload']['tmp_name'];
+
+ // Store all uploaded files in the flat tree storage area
+ if( empty( $pStoreRow['upload']['dest_branch'] )) {
+ $pStoreRow['upload']['dest_branch'] = mime_default_branch( $pStoreRow['attachment_id'] );
+ // Use group number to protect disk content - still need to set group of directory!
+ KernelTools::mkdir_p( STORAGE_PKG_PATH.$pStoreRow['upload']['dest_branch'], 0777 );
+ }
+ $ret = true;
+ } else {
+ $pStoreRow['errors']['upload'] = KernelTools::tra( 'There was a problem verifying the uploaded file.' );
+ }
+
+ return $ret;
+ }
+}
+
+/**
+ * When a file is edited
+ *
+ * @param array $pStoreRow File data needed to store details in the database - sanitised and generated in the verify function
+ * @access public
+ * @return bool true on success, false on failure - $pStoreRow['errors'] will contain reason
+ */
+if( !function_exists( '\Bitweaver\Liberty\mime_default_update' )) {
+ function mime_default_update( &$pStoreRow ) {
+ global $gBitSystem;
+
+ // this will reset the uploaded file
+ if( BitBase::verifyId( $pStoreRow['attachment_id'] ) && !empty( $pStoreRow['upload'] )) {
+ if( empty( $pStoreRow['dest_branch'] )) {
+ $pStoreRow['dest_branch'] = liberty_mime_get_storage_branch( [ 'attachment_id' => $pStoreRow['attachment_id'] ] );
+ }
+
+ if( !empty( $pStoreRow['dest_branch'] ) && !empty( $pStoreRow['file_name'] ) ) {
+ // First we remove the old file
+ $path = STORAGE_PKG_PATH.$pStoreRow['dest_branch'];
+ $file = $path.$pStoreRow['file_name'];
+ if(( $nuke = LibertyMime::validateStoragePath( $path )) && is_dir( $nuke )) {
+ if( !empty( $pStoreRow['unlink_dir'] )) {
+ @KernelTools::unlink_r( $path );
+ mkdir( $path );
+ } else {
+ @\unlink( $file );
+ }
+ }
+
+ // make sure we store the new file in the same place as before
+ $pStoreRow['upload']['dest_branch'] = $pStoreRow['dest_branch'];
+
+ // if we can create new thumbnails for this file, we remove the old ones first
+ $canThumbFunc = liberty_get_function( 'can_thumbnail' );
+ if( !empty( $canThumbFunc ) && $canThumbFunc( $pStoreRow['upload']['type'] )) {
+ liberty_clear_thumbnails( $pStoreRow['upload'] );
+ }
+
+ // Now we process the uploaded file
+ if( $storagePath = liberty_process_upload( $pStoreRow['upload'] )) {
+ $sql = "UPDATE `".BIT_DB_PREFIX."liberty_files` SET `file_name` = ?, `mime_type` = ?, `file_size` = ?, `user_id` = ? WHERE `file_id` = ?";
+ $gBitSystem->mDb->query( $sql, [ $pStoreRow['upload']['name'], $pStoreRow['upload']['type'], $pStoreRow['upload']['size'], $pStoreRow['user_id'], $pStoreRow['file_id'] ] );
+ }
+
+ // ensure we have the correct guid in the db
+ if( empty( $pStoreRow['attachment_plugin_guid'] )) {
+ $pStoreRow['attachment_plugin_guid'] = LIBERTY_DEFAULT_MIME_HANDLER;
+ }
+
+ $gBitSystem->mDb->associateUpdate(
+ BIT_DB_PREFIX."liberty_attachments",
+ [ 'attachment_plugin_guid' => $pStoreRow['attachment_plugin_guid'] ],
+ [ 'attachment_id' => $pStoreRow['attachment_id'] ]
+ );
+ }
+ }
+ return true;
+ }
+}
+
+/**
+ * Store the data in the database
+ *
+ * @param array $pStoreRow File data needed to store details in the database - sanitised and generated in the verify function
+ * @access public
+ * @return bool true on success, false on failure - $pStoreRow['errors'] will contain reason
+ */
+if( !function_exists( '\Bitweaver\Liberty\mime_default_store' )) {
+ function mime_default_store( &$pStoreRow ) {
+ global $gBitSystem, $gLibertySystem;
+ $ret = false;
+ // take care of the uploaded file and insert it into the liberty_files and liberty_attachments tables
+ if( $storagePath = liberty_process_upload( $pStoreRow['upload'], empty( $pStoreRow['upload']['copy_file'] ))) {
+ // add row to liberty_files
+ $storeHash = [
+ "file_name" => $pStoreRow['upload']['name'],
+ "file_id" => $pStoreRow['attachment_id'],
+ "mime_type" => $pStoreRow['upload']['type'],
+ "file_size" => $pStoreRow['upload']['size'],
+ "user_id" => $pStoreRow['user_id'],
+ ];
+ if ( !$storeHash['file_size'] ) { $storeHash['file_size'] = 0; }
+
+ $gBitSystem->mDb->associateInsert( BIT_DB_PREFIX."liberty_files", $storeHash );
+
+ // add the data into liberty_attachments to make this file available as attachment
+ $storeHash = [
+ "attachment_id" => $pStoreRow['attachment_id'],
+ "content_id" => $pStoreRow['content_id'],
+ "attachment_plugin_guid" => !empty( $pStoreRow['attachment_plugin_guid'] ) ? $pStoreRow['attachment_plugin_guid'] : PLUGIN_MIME_GUID_FLATDEFAULT,
+ "foreign_id" => $pStoreRow['attachment_id'],
+ "user_id" => $pStoreRow['user_id'],
+ ];
+ $gBitSystem->mDb->associateInsert( BIT_DB_PREFIX."liberty_attachments", $storeHash );
+
+ $ret = true;
+ } else {
+ $pStoreRow['errors']['liberty_process'] = "There was a problem processing the file.";
+ }
+ return $ret;
+ }
+}
+
+/**
+ * Load file data from the database
+ *
+ * @param array $pFileHash contains all file information
+ * @access public
+ * @return bool true on success, false on failure - ['errors'] will contain reason for failure
+ */
+if( !function_exists( '\Bitweaver\Liberty\mime_default_load' )) {
+ function mime_default_load( $pFileHash, &$pPrefs ) {
+ global $gBitSystem, $gLibertySystem;
+ $ret = false;
+ if( BitBase::verifyId( $pFileHash['attachment_id'] )) {
+ $query = "
+ SELECT *
+ FROM `".BIT_DB_PREFIX."liberty_attachments` la
+ LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_files` lf ON( la.`foreign_id` = lf.`file_id` )
+ WHERE la.`attachment_id`=?";
+ if( $row = $gBitSystem->mDb->getRow( $query, [ $pFileHash['attachment_id'] ] ) ) {
+ $ret = array_merge( $pFileHash, $row );
+ $storageName = basename( $row['file_name'] );
+ // compatibility with _FILES hash
+ $row['name'] = $storageName;
+ $row['type'] = $row['mime_type'];
+ $storageBranch = liberty_mime_get_storage_branch( [ 'attachment_id' => $row['attachment_id'] ] ).$storageName;
+ // this will fetch the correct thumbnails
+ $thumbHash['source_file'] = STORAGE_PKG_PATH.$storageBranch;
+ $row['source_file'] = STORAGE_PKG_PATH.$storageBranch;
+ $canThumbFunc = liberty_get_function( 'can_thumbnail' );
+ if( $canThumbFunc && $canThumbFunc( $row['mime_type'] )) {
+ $thumbHash['default_image'] = LIBERTY_PKG_URL.'icons/generating_thumbnails.png';
+ }
+ $ret['thumbnail_url'] = liberty_fetch_thumbnails( $thumbHash );
+ // indicate that this is a mime thumbnail
+ if( !empty( $ret['thumbnail_url']['medium'] ) && strpos( $ret['thumbnail_url']['medium'], '/mime/' )) {
+ $ret['thumbnail_is_mime'] = true;
+ }
+
+ // pretty URLs
+ $ret['display_url'] = $gBitSystem->isFeatureActive( "pretty_urls" ) || $gBitSystem->isFeatureActive( "pretty_urls_extended" )
+ ? LIBERTY_PKG_URL."view/file/".$row['attachment_id']
+ : LIBERTY_PKG_URL."view_file.php?attachment_id=".$row['attachment_id'];
+
+ // legacy table data was named storage path and included a partial path. strip out any path just in case
+ $ret['file_name'] = $storageName;
+ $ret['preferences'] = $pPrefs;
+
+ // some stuff is only available if we have a source file
+ // make sure to check for these when you use them. frequently the original might not be available
+ // e.g.: video files are large and the original might be deleted after conversion
+ if( is_file( STORAGE_PKG_PATH.$storageBranch )) {
+ $ret['source_file'] = STORAGE_PKG_PATH.$storageBranch;
+ $ret['source_url'] = STORAGE_PKG_URL.$storageBranch;
+ $ret['last_modified'] = filemtime( $ret['source_file'] );
+ $ret['download_url'] = $gBitSystem->isFeatureActive( "pretty_urls" ) || $gBitSystem->isFeatureActive( "pretty_urls_extended" )
+ ? LIBERTY_PKG_URL."download/file/".$row['attachment_id']
+ : LIBERTY_PKG_URL."download_file.php?attachment_id=".$row['attachment_id'];
+ }
+
+ // add a description of how to insert this file into a wiki page
+ if( $gLibertySystem->isPluginActive( 'dataattachment' )) {
+ $ret['wiki_plugin_link'] = "{image id=".$row['attachment_id']."}";
+ }
+
+ // additionally we'll add this to distinguish between old plugins and new ones
+ // TODO: this should hopefully not be necessary for too long
+ $ret['is_mime'] = true;
+ }
+ }
+ return $ret;
+ }
+}
+
+/**
+ * Takes care of the entire download process. Make sure it doesn't die at the end.
+ * in this functioin it would be possible to add download resume possibilites and the like
+ *
+ * @param array $pFileHash Basically the same has as returned by the load function
+ * @access public
+ * @return bool true on success, false on failure - $pParamHash['errors'] will contain reason for failure
+ */
+if( !function_exists( '\Bitweaver\Liberty\mime_default_download' )) {
+ function mime_default_download( &$pFileHash ) {
+ global $gBitSystem;
+ $ret = false;
+
+ // Check to see if the file actually exists
+ if( !empty( $pFileHash['source_file'] ) && is_readable( $pFileHash['source_file'] )) {
+ // make sure we close off obzip compression if it's on
+ if( $gBitSystem->isFeatureActive( 'site_output_obzip' )) {
+ @ob_end_clean();
+ }
+
+ // this will get the browser to open the download dialogue - even when the
+ // browser could deal with the content type - not perfect, but works
+ if( $gBitSystem->isFeatureActive( 'mime_force_download' )) {
+ $pFileHash['mime_type'] = "application/force-download";
+ }
+
+ // set up header
+ header( "Cache-Control: no-cache,must-revalidate" );
+ header( "Expires: 0" );
+ header( "Accept-Ranges: bytes" );
+ header( "Pragma: public" );
+ header( "Last-Modified: ".gmdate( "D, d M Y H:i:s T", $pFileHash['last_modified'] ), true, 200 );
+ header( 'Content-Disposition: attachment; filename="'.$pFileHash['file_name'].'"' );
+ header( "Content-type: ".$pFileHash['mime_type'] );
+ header( "Content-Description: File Transfer" );
+ header( "Content-Length: ".filesize( $pFileHash['source_file'] ));
+ header( "Content-Transfer-Encoding: binary" );
+ //header( "Connection: close" );
+
+ @ob_clean();
+ flush();
+ readfile( $pFileHash['source_file'] );
+ $ret = true;
+ } else {
+ $pFileHash['errors']['no_file'] = KernelTools::tra( 'No matching file found.' );
+ }
+ return $ret;
+ }
+}
+
+/**
+ * Nuke data in tables when content is removed
+ *
+ * @param integer $pAttachmentId The id of the attachment to delete
+ * @access public
+ * @return bool true on success, false on failure
+ */
+if( !function_exists( '\Bitweaver\Liberty\mime_default_expunge' )) {
+ function mime_default_expunge( $pAttachmentId ) {
+ global $gBitSystem, $gBitUser;
+ $ret = false;
+ if( BitBase::verifyId( $pAttachmentId )) {
+ if( $fileHash = LibertyMime::loadAttachment( $pAttachmentId )) {
+ if( $gBitUser->isAdmin() || $gBitUser->mUserId == $fileHash['user_id'] && isset( $fileHash['source_file'] ) && !empty( $fileHash['source_file'] )) {
+ // make sure this is a valid storage directory before removing it
+ if( preg_match( "#^".realpath( STORAGE_PKG_PATH )."/attachments/\d+/\d+/#", $fileHash['source_file'] ) && is_file( $fileHash['source_file'] )) {
+ KernelTools::unlink_r( dirname( $fileHash['source_file'] ));
+ }
+ $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_files` WHERE `file_id` = ?";
+ $gBitSystem->mDb->query( $query, [ $fileHash['foreign_id'] ] );
+ $ret = true;
+ }
+ }
+ }
+ return $ret;
+ }
+}
+
+/**
+ * Generate branch from Id
+ *
+ * @param integer $pAttachmentId The id of the attachment to access
+ * @access public
+ * @return string containing path to storage location for attachment
+ */
+if( !function_exists( '\Bitweaver\Liberty\mime_default_branch' )) {
+ function mime_default_branch( $pAttachmentId ) {
+ $ret = false;
+ if( BitBase::verifyId( $pAttachmentId ) ) {
+ $ret = FLAT_STORAGE_NAME.'/'.($pAttachmentId % 1000).'/'.$pAttachmentId.'/';
+ }
+ 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 Lester Caine<lester@lsces.co.uk>
+ * @param $pParamHash key=>value pairs to determine path. Possible keys in descending directory depth are: 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( '\Bitweaver\Liberty\liberty_mime_get_storage_branch' )) {
+ function liberty_mime_get_storage_branch( $pParamHash ) { // $pSubDir = null, $pUserId = null, $pPackage = $gBitSystem->getActivePackage(), $pPermissions = 0755, $pCreateDir = true ) {
+ // *PRIVATE FUNCTION. GO AWAY! DO NOT CALL DIRECTLY!!!
+ global $gBitSystem;
+ $pathParts = [];
+
+ if( $pAttachmentId = BitBase::getParameter( $pParamHash, 'attachment_id' ) ) {
+ $pathParts[] = 'attachments';
+ $pathParts[] = (int)($pAttachmentId % 1000);
+ $pathParts[] = $pAttachmentId;
+// Added in fisheye override
+ } else {
+ $pathParts[] = 'common';
+ }
+
+ if( $pPackage = BitBase::getParameter( $pParamHash, 'package' ) ) {
+ $pathParts[] = $pPackage;
+ }
+ $fullPath = implode( '/', $pathParts ).'/';
+ if( BitBase::getParameter( $pParamHash, 'create_dir', true ) ){
+ if( !file_exists( STORAGE_PKG_PATH.$fullPath ) ) {
+ KernelTools::mkdir_p( STORAGE_PKG_PATH.$fullPath );
+ }
+ }
+ return $fullPath;
+ }
+}
+
+if( !function_exists( '\Bitweaver\Liberty\liberty_mime_get_source_url' )) {
+ function liberty_mime_get_source_url( $pParamHash ) {
+ if( empty( $pParamHash['package'] ) ) {
+ $pParamHash['package'] = \Bitweaver\Liberty\liberty_mime_get_storage_sub_dir_name( [ 'type' => BitBase::getParameter( $pParamHash, 'mime_type' ), 'name' => BitBase::getParameter( $pParamHash, 'file_name' ) ] );
+ }
+ if( empty( $pParamHash['sub_dir'] ) ) {
+ $pParamHash['sub_dir'] = BitBase::getParameter( $pParamHash, 'attachment_id' );
+ }
+ return STORAGE_PKG_URL.liberty_mime_get_storage_branch( $pParamHash ).basename( BitBase::getParameter( $pParamHash, 'file_name' ) );
+ }
+}
+
+if( !function_exists( '\Bitweaver\Liberty\liberty_mime_get_source_file' )) {
+ function liberty_mime_get_source_file( $pParamHash ) {
+ if( empty( $pParamHash['package'] ) ) {
+ $pParamHash['package'] = \Bitweaver\Liberty\liberty_mime_get_storage_sub_dir_name( [ 'type' => BitBase::getParameter( $pParamHash, 'mime_type' ), 'name' => BitBase::getParameter( $pParamHash, 'file_name' ) ] );
+ }
+ if( empty( $pParamHash['sub_dir'] ) ) {
+ $pParamHash['sub_dir'] = BitBase::getParameter( $pParamHash, 'attachment_id' );
+ }
+ return STORAGE_PKG_PATH.liberty_mime_get_storage_branch( $pParamHash ).basename( BitBase::getParameter( $pParamHash, 'file_name' ) );
+ }
+}
diff --git a/templates/choose_protection.tpl b/templates/choose_protection.tpl
new file mode 100755
index 0000000..9084600
--- /dev/null
+++ b/templates/choose_protection.tpl
@@ -0,0 +1,18 @@
+{strip}
+<div class="form-group">
+ {formlabel label="Protection Level"}
+ {forminput}
+ {if $gBitSystem->isFeatureActive( 'protector_single_role' )}
+ Assign content to the following user role:<br/>
+ {html_options name="$protector[role_id]" options=$protectorRolesId selected=$serviceHash.protector.role_id}
+ {formhelp note="Users assigned to this role can view this content item."}
+ {else}
+ Limit access to the following user roles:<br/>
+ {foreach from=$protectorRoles key=roleId item=role}
+ <input type="checkbox" name="$protector[role_id][]" value="{$roleId}" {if isset($serviceHash.protector.role.$roleId)}checked="checked"{/if} /> {$role.role_name}<br/>
+ {/foreach}
+ {formhelp note="Users assigned to these roles can view this content item."}
+ {/if}
+ {/forminput}
+</div>
+{/strip} \ No newline at end of file
diff --git a/templates/protector_service_icon.tpl b/templates/protector_service_icon.tpl
new file mode 100755
index 0000000..83452a5
--- /dev/null
+++ b/templates/protector_service_icon.tpl
@@ -0,0 +1,16 @@
+{if !$serviceHash and $gContent->mInfo}
+ {assign var=serviceHash value=$gContent->mInfo}
+{/if}
+
+{if !empty($serviceHash.is_hidden) and $serviceHash.is_hidden=='y'}
+ {assign var=securityLabel value="Hidden"}
+{/if}
+{if !empty($serviceHash.is_private) and $serviceHash.is_private=='y'}
+ {assign var=securityLabel value="Private"}
+{/if}
+{if !empty($serviceHash.access_answer) and $serviceHash.access_answer}
+ {assign var=securityLabel value="Password Required"}
+{/if}
+{if !empty($securityLabel)}
+ {booticon iname="icon-lock" ipackage="icons" iexplain=$securityLabel iforce=icon_text}
+{/if}