1717 lines
46 KiB
PHP
1717 lines
46 KiB
PHP
<?php
|
|
/**
|
|
* @brief Databases Model
|
|
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
|
* @copyright (c) Invision Power Services, Inc.
|
|
* @license https://www.invisioncommunity.com/legal/standards/
|
|
* @package Invision Community
|
|
* @subpackage Content
|
|
* @since 31 March 2014
|
|
*/
|
|
|
|
namespace IPS\cms;
|
|
|
|
/* To prevent PHP errors (extending class does not exist) revealing path */
|
|
|
|
use DateInterval;
|
|
use Exception;
|
|
use IPS\Application;
|
|
use IPS\cms\Pages\Page;
|
|
use IPS\Content\Search\Index;
|
|
use IPS\Content\ViewUpdates;
|
|
use IPS\Data\Store;
|
|
use IPS\DateTime;
|
|
use IPS\Db;
|
|
use IPS\Dispatcher;
|
|
use IPS\File;
|
|
use IPS\Http\Url;
|
|
use IPS\IPS;
|
|
use IPS\Lang;
|
|
use IPS\Member;
|
|
use IPS\Member\Group;
|
|
use IPS\Node\Model;
|
|
use IPS\Node\Permissions;
|
|
use IPS\Patterns\ActiveRecord;
|
|
use IPS\Patterns\ActiveRecordIterator;
|
|
use IPS\Platform\Bridge;
|
|
use IPS\Request;
|
|
use IPS\Settings;
|
|
use IPS\Task;
|
|
use IPS\Widget\Area;
|
|
use LogicException;
|
|
use OutOfBoundsException;
|
|
use OutOfRangeException;
|
|
use function count;
|
|
use function defined;
|
|
use function get_class;
|
|
use function in_array;
|
|
use function is_array;
|
|
|
|
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
|
{
|
|
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* @brief Databases Model
|
|
*/
|
|
class Databases extends Model implements Permissions
|
|
{
|
|
use ViewUpdates;
|
|
|
|
/**
|
|
* @brief [ActiveRecord] Multiton Store
|
|
*/
|
|
protected static array $multitons = array();
|
|
|
|
/**
|
|
* @brief [ActiveRecord] Caches
|
|
* @note Defined cache keys will be cleared automatically as needed
|
|
*/
|
|
protected array $caches = array( 'database_reciprocal_links', 'cms_databases' );
|
|
|
|
/**
|
|
* @brief [ActiveRecord] Database Prefix
|
|
*/
|
|
public static string $databasePrefix = 'database_';
|
|
|
|
/**
|
|
* @brief [ActiveRecord] ID Database Table
|
|
*/
|
|
public static ?string $databaseTable = 'cms_databases';
|
|
|
|
/**
|
|
* @brief [ActiveRecord] ID Database Column
|
|
*/
|
|
public static string $databaseColumnId = 'id';
|
|
|
|
/**
|
|
* @brief [ActiveRecord] Database ID Fields
|
|
*/
|
|
protected static array $databaseIdFields = array( 'database_key', 'database_page_id' );
|
|
|
|
/**
|
|
* @brief [ActiveRecord] Multiton Map
|
|
*/
|
|
protected static array $multitonMap = array();
|
|
|
|
/**
|
|
* @brief [Node] Parent ID Database Column
|
|
*/
|
|
public static ?string $databaseColumnOrder = 'id';
|
|
|
|
/**
|
|
* @brief [Node] Sortable?
|
|
*/
|
|
public static bool $nodeSortable = FALSE;
|
|
|
|
/**
|
|
* @brief [Node] Node Title
|
|
*/
|
|
public static string $nodeTitle = '';
|
|
|
|
/**
|
|
* @brief Have fetched all?
|
|
*/
|
|
protected static bool $gotAll = FALSE;
|
|
|
|
/**
|
|
* @brief The map of permission columns
|
|
*/
|
|
public static array $permissionMap = array(
|
|
'view' => 'view',
|
|
'read' => 2,
|
|
'add' => 3,
|
|
'edit' => 4,
|
|
'reply' => 5,
|
|
'review' => 7,
|
|
'rate' => 6
|
|
);
|
|
|
|
/**
|
|
* @brief [Node] App for permission index
|
|
*/
|
|
public static ?string $permApp = 'cms';
|
|
|
|
/**
|
|
* @brief [Node] Type for permission index
|
|
*/
|
|
public static ?string $permType = 'databases';
|
|
|
|
/**
|
|
* @brief [Node] Prefix string that is automatically prepended to permission matrix language strings
|
|
*/
|
|
public static string $permissionLangPrefix = 'perm_content_';
|
|
|
|
/**
|
|
* @brief [Node] Show forms modally?
|
|
*/
|
|
public static bool $modalForms = FALSE;
|
|
|
|
/**
|
|
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
|
|
*/
|
|
public static ?string $titleLangPrefix = 'content_db_';
|
|
|
|
/**
|
|
* [Brief] Bump on edit only
|
|
*/
|
|
const BUMP_ON_EDIT = 1;
|
|
|
|
/**
|
|
* [Brief] Bump on comment only
|
|
*/
|
|
const BUMP_ON_COMMENT = 2;
|
|
|
|
/**
|
|
* [Brief] Bump on edit only
|
|
*/
|
|
const CATEGORY_VIEW_CATEGORIES = 0;
|
|
|
|
/**
|
|
* [Brief] Bump on comment only
|
|
*/
|
|
const CATEGORY_VIEW_FEATURED = 1;
|
|
|
|
/**
|
|
* [Brief] Database template groups
|
|
*/
|
|
public static array $templateGroups = array(
|
|
'categories' => 'category_index',
|
|
'index' => 'category_articles',
|
|
'listing' => 'listing',
|
|
'display' => 'display',
|
|
'form' => 'form'
|
|
);
|
|
|
|
/**
|
|
* @brief Bitwise values for database_options field
|
|
*/
|
|
public static array $bitOptions = array(
|
|
'options' => array(
|
|
'options' => array(
|
|
'comments' => 1, // Enable comments?
|
|
'reviews' => 2, // Enable reviews?
|
|
'comments_mod' => 4, // Enable comment moderation?
|
|
'reviews_mod' => 8, // Enable reviews moderation?
|
|
'indefinite_own_edit' => 16, // Enable authors to indefinitely edit their own articles
|
|
'assignments' => 32, // Enable assignments on this database
|
|
)
|
|
)
|
|
);
|
|
|
|
/**
|
|
* Mapping of node columns to specific actions (e.g. comment, review)
|
|
* Note: Mappings can also reference bitoptions keys.
|
|
*
|
|
* @var array
|
|
*/
|
|
public static array $actionColumnMap = array(
|
|
'comments' => 'comments',
|
|
'reviews' => 'reviews',
|
|
'moderate_comments' => 'comments_mod',
|
|
'moderate_items' => 'record_approve',
|
|
'moderate_reviews' => 'reviews_mod',
|
|
'tags' => 'tags_enabled',
|
|
'prefix' => 'tags_noprefixes'
|
|
);
|
|
|
|
/**
|
|
* @brief Page title
|
|
*/
|
|
protected ?string $pageTitle = NULL;
|
|
|
|
/**
|
|
* @breif Used by the dataLayer
|
|
*/
|
|
public static string $contentArea = 'pages_database';
|
|
|
|
/**
|
|
* @breif Used by the dataLayer
|
|
*/
|
|
public static string $containerType = 'pages_database_category';
|
|
|
|
public static bool $_hasLoadedPerms = false;
|
|
|
|
/**
|
|
* Determines if this class can be extended via UI Extension
|
|
*
|
|
* @var bool
|
|
*/
|
|
public static bool $canBeExtended = true;
|
|
|
|
/**
|
|
* Get the properties that can be added to the datalayer for this key
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getDataLayerProperties(): array
|
|
{
|
|
if ( empty( $this->_dataLayerProperties ) )
|
|
{
|
|
$properties = parent::getDataLayerProperties();
|
|
$properties['content_area'] = $this->_title;
|
|
$properties['container_type'] = "{$this->key}_database";
|
|
$this->_dataLayerProperties = $properties;
|
|
}
|
|
|
|
return $this->_dataLayerProperties;
|
|
}
|
|
|
|
/**
|
|
* Check the action column map if the action is enabled in this node
|
|
*
|
|
* @param string $action
|
|
* @return bool
|
|
*/
|
|
public function checkAction( string $action ) : bool
|
|
{
|
|
$return = parent::checkAction( $action );
|
|
|
|
/* Some actions here are reversed, we mark them as disabled instead of enabled */
|
|
if( $action == 'prefix' )
|
|
{
|
|
return !$return;
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Load Record
|
|
*
|
|
* @param int|string|null $id ID
|
|
* @param string|null $idField The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
|
|
* @param mixed $extraWhereClause Additional where clause(s) (see \IPS\Db::build for details) - if used will cause multiton store to be skipped and a query always ran
|
|
* @return ActiveRecord|Databases
|
|
* @see Db::build
|
|
*/
|
|
public static function load( int|string|null $id, string $idField=NULL, mixed $extraWhereClause=NULL ): ActiveRecord|static
|
|
{
|
|
if ( ! Bridge::i()->pagesAllowDatabaseAccess() )
|
|
{
|
|
throw new OutOfRangeException('pages_not_available');
|
|
}
|
|
|
|
return parent::load( $id, $idField, $extraWhereClause );
|
|
}
|
|
|
|
/**
|
|
* Return all databases
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function databases(): array
|
|
{
|
|
if ( ! Bridge::i()->pagesAllowDatabaseAccess() )
|
|
{
|
|
static::$gotAll = true;
|
|
static::$multitons = [];
|
|
}
|
|
else
|
|
{
|
|
if ( ! static::$gotAll )
|
|
{
|
|
/* Avoid using SHOW TABLES LIKE / checkForTable() */
|
|
try
|
|
{
|
|
foreach( static::getStore() as $db )
|
|
{
|
|
$id = $db[ static::$databasePrefix . static::$databaseColumnId ];
|
|
static::$multitons[ $id ] = static::constructFromData( $db );
|
|
}
|
|
}
|
|
catch( Exception $e ) { }
|
|
|
|
static::$gotAll = true;
|
|
}
|
|
}
|
|
|
|
return static::$multitons;
|
|
}
|
|
|
|
/**
|
|
* Returns database data from the store
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function getStore(): array
|
|
{
|
|
if ( ! isset( Store::i()->cms_databases ) )
|
|
{
|
|
Store::i()->cms_databases = iterator_to_array(
|
|
Db::i()->select(
|
|
static::$databaseTable . '.*, core_permission_index.perm_id, core_permission_index.perm_view, core_permission_index.perm_2, core_permission_index.perm_3, core_permission_index.perm_4, core_permission_index.perm_5, core_permission_index.perm_6, core_permission_index.perm_7',
|
|
static::$databaseTable
|
|
)->join(
|
|
'core_permission_index',
|
|
array( "core_permission_index.app=? AND core_permission_index.perm_type=? AND core_permission_index.perm_type_id=" . static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnId, static::$permApp, static::$permType )
|
|
)
|
|
->setKeyField('database_id')
|
|
);
|
|
}
|
|
|
|
return Store::i()->cms_databases;
|
|
}
|
|
|
|
/**
|
|
* Construct ActiveRecord from database row
|
|
*
|
|
* @param array $data Row from database table
|
|
* @param bool $updateMultitonStoreIfExists Replace current object in multiton store if it already exists there?
|
|
* @return static
|
|
*/
|
|
public static function constructFromData( array $data, bool $updateMultitonStoreIfExists = TRUE ): static
|
|
{
|
|
$obj = parent::constructFromData( $data, $updateMultitonStoreIfExists );
|
|
$obj->preLoadWords();
|
|
|
|
return $obj;
|
|
}
|
|
|
|
|
|
/**
|
|
* Can this database accept RSS imports?
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function canImportRss(): bool
|
|
{
|
|
if ( ! $this->page_id )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
$fieldsClass = '\IPS\cms\Fields' . $this->id;
|
|
/* @var $fieldsClass Fields */
|
|
try
|
|
{
|
|
if ( ! in_array( IPS::mb_ucfirst( $fieldsClass::load( $this->field_title )->type ), array( 'Text', 'TextArea', 'Editor' ) ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( ! in_array( IPS::mb_ucfirst( $fieldsClass::load( $this->field_content )->type ), array( 'TextArea', 'Editor' ) ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
catch( Exception $e )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Return data for the ACP Menu
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function acpMenu(): array
|
|
{
|
|
$menu = array();
|
|
|
|
foreach(
|
|
Db::i()->select( '*, core_sys_lang_words.word_custom as database_name, core_sys_lang_words2.word_custom as record_name', 'cms_databases', NULL, 'core_sys_lang_words.word_custom' )
|
|
->join( 'core_sys_lang_words', "core_sys_lang_words.word_key=CONCAT( 'content_db_', cms_databases.database_id ) AND core_sys_lang_words.lang_id=" . Member::loggedIn()->language()->id )
|
|
->join( array( 'core_sys_lang_words', 'core_sys_lang_words2' ), "core_sys_lang_words2.word_key=CONCAT( 'content_db_lang_pu_', cms_databases.database_id ) AND core_sys_lang_words2.lang_id=" . Member::loggedIn()->language()->id )
|
|
as $row )
|
|
{
|
|
$menu[] = array(
|
|
'id' => $row['database_id'],
|
|
'title' => $row['database_name'],
|
|
'record_name' => $row['record_name'],
|
|
'use_categories' => $row['database_use_categories']
|
|
);
|
|
}
|
|
|
|
return $menu;
|
|
}
|
|
|
|
/**
|
|
* Checks and fixes existing DB
|
|
*
|
|
* @param int $id Database ID
|
|
* @return int $fixes Number of fixes made (0 if none)
|
|
*
|
|
* @throws OutOfRangeException
|
|
*/
|
|
public static function checkandFixDatabaseSchema(int $id ): int
|
|
{
|
|
$fixes = 0;
|
|
$json = json_decode( @file_get_contents( \IPS\ROOT_PATH . "/applications/cms/data/databaseschema.json" ), true );
|
|
$table = $json['cms_custom_database_1'];
|
|
$tableName = 'cms_custom_database_' . $id;
|
|
|
|
if ( ! Db::i()->checkForTable( $tableName ) )
|
|
{
|
|
throw new OutOfRangeException;
|
|
}
|
|
|
|
$schema = Db::i()->getTableDefinition( $tableName );
|
|
$changes = array();
|
|
|
|
/* Columns */
|
|
foreach( $table['columns'] as $key => $data )
|
|
{
|
|
if ( ! isset( $schema['columns'][ $key ] ) )
|
|
{
|
|
$changes[] = "ADD COLUMN " . Db::i()->compileColumnDefinition( $data );
|
|
$fixes++;
|
|
}
|
|
}
|
|
|
|
/* Indexes */
|
|
foreach( $table['indexes'] as $key => $data )
|
|
{
|
|
/* No index */
|
|
if ( ! isset( $schema['indexes'][ $key ] ) )
|
|
{
|
|
$changes[] = Db::i()->buildIndex( $tableName, $data );
|
|
$fixes++;
|
|
}
|
|
else if ( implode( '.', $data['columns'] ) != implode( '.', (array) $schema['indexes'][ $key ]['columns'] ) )
|
|
{
|
|
/* Check columns */
|
|
if( $key == 'PRIMARY KEY' )
|
|
{
|
|
$changes[] = "DROP PRIMARY KEY";
|
|
}
|
|
else
|
|
{
|
|
$changes[] = "DROP KEY `" . Db::i()->escape_string( $key ) . "`";
|
|
}
|
|
|
|
$changes[] = Db::i()->buildIndex( $tableName, $data );
|
|
$fixes++;
|
|
}
|
|
}
|
|
|
|
/* We collect all the changes so we can run one database query instead of, potentially, dozens */
|
|
if( count( $changes ) )
|
|
{
|
|
Db::i()->query( "ALTER TABLE " . Db::i()->prefix . $tableName . " " . implode( ', ', $changes ) );
|
|
}
|
|
|
|
return $fixes;
|
|
}
|
|
|
|
/**
|
|
* Create a new database
|
|
*
|
|
* @param Databases $database ID of database to create
|
|
* @return void
|
|
*/
|
|
public static function createDatabase( Databases $database ) : void
|
|
{
|
|
$json = json_decode( @file_get_contents( \IPS\ROOT_PATH . "/applications/cms/data/databaseschema.json" ), true );
|
|
$table = $json['cms_custom_database_1'];
|
|
|
|
$table['name'] = 'cms_custom_database_' . $database->id;
|
|
|
|
foreach( $table['columns'] as $name => $data )
|
|
{
|
|
if ( mb_substr( $name, 0, 6 ) === 'field_' )
|
|
{
|
|
unset( $table['columns'][ $name ] );
|
|
}
|
|
}
|
|
|
|
foreach( $table['indexes'] as $name => $data )
|
|
{
|
|
if ( mb_substr( $name, 0, 6 ) === 'field_' )
|
|
{
|
|
unset( $table['indexes'][ $name ] );
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
if ( ! Db::i()->checkForTable( $table['name'] ) )
|
|
{
|
|
Db::i()->createTable( $table );
|
|
}
|
|
}
|
|
catch( Db\Exception $ex )
|
|
{
|
|
throw new LogicException( $ex );
|
|
}
|
|
|
|
/* Populate default custom fields */
|
|
$fieldsClass = 'IPS\cms\Fields' . $database->id;
|
|
$fieldTitle = array();
|
|
$fieldContent = array();
|
|
$catTitle = array();
|
|
$catDesc = array();
|
|
|
|
foreach( Lang::languages() as $id => $lang )
|
|
{
|
|
/* Try to get the actual database noun if it has been created */
|
|
try
|
|
{
|
|
$title = $lang->get( 'content_db_lang_pu_' . $database->id );
|
|
}
|
|
catch( Exception $e )
|
|
{
|
|
$title = $lang->get('content_database_noun_pu');
|
|
}
|
|
|
|
$fieldTitle[ $id ] = $lang->get('content_fields_is_title');
|
|
$fieldContent[ $id ] = $lang->get('content_fields_is_content');
|
|
$catTitle[ $id ] = $title;
|
|
$catDesc[ $id ] = '';
|
|
}
|
|
|
|
/* Title */
|
|
$titleField = new $fieldsClass;
|
|
$titleField->saveForm( $titleField->formatFormValues( array(
|
|
'field_title' => $fieldTitle,
|
|
'field_type' => 'Text',
|
|
'field_key' => 'titlefield_' . $database->id,
|
|
'field_required' => 1,
|
|
'field_user_editable' => 1,
|
|
'field_display_listing' => 1,
|
|
'field_display_display' => 1,
|
|
'field_is_searchable' => 1,
|
|
'field_max_length' => 255
|
|
) ) );
|
|
|
|
$database->field_title = $titleField->id;
|
|
$perms = $titleField->permissions();
|
|
|
|
Db::i()->update( 'core_permission_index', array(
|
|
'perm_view' => '*',
|
|
'perm_2' => '*',
|
|
'perm_3' => '*'
|
|
), array( 'perm_id=?', $perms['perm_id']) );
|
|
|
|
/* Content */
|
|
$contentField = new $fieldsClass;
|
|
$contentField->saveForm( $contentField->formatFormValues( array(
|
|
'field_title' => $fieldContent,
|
|
'field_type' => 'Editor',
|
|
'field_key' => 'contentfield_' . $database->id,
|
|
'field_required' => 1,
|
|
'field_user_editable' => 1,
|
|
'field_truncate' => 100,
|
|
'field_topic_format' => '{value}',
|
|
'field_display_listing' => 1,
|
|
'field_display_display' => 1,
|
|
'field_is_searchable' => 1
|
|
) ) );
|
|
|
|
$database->field_content = $contentField->id;
|
|
$perms = $contentField->permissions();
|
|
|
|
Db::i()->update( 'core_permission_index', array(
|
|
'perm_view' => '*',
|
|
'perm_2' => '*',
|
|
'perm_3' => '*'
|
|
), array( 'perm_id=?', $perms['perm_id']) );
|
|
|
|
/* Save the db now to save custom field ids */
|
|
$database->save();
|
|
|
|
/* Create a category */
|
|
$categoryClass = '\IPS\cms\Categories' . $database->id;
|
|
$category = new $categoryClass;
|
|
$category->database_id = $database->id;
|
|
|
|
$category->saveForm( $category->formatFormValues( array(
|
|
'category_name' => $catTitle,
|
|
'category_description' => $catDesc,
|
|
'category_parent_id' => 0,
|
|
'category_has_perms' => 0,
|
|
'category_show_records' => 1,
|
|
'category_image' => null
|
|
) ) );
|
|
|
|
$perms = $category->permissions();
|
|
|
|
Db::i()->update( 'core_permission_index', array(
|
|
'perm_view' => '*',
|
|
'perm_2' => '*',
|
|
'perm_3' => '*'
|
|
), array( 'perm_id=?', $perms['perm_id']) );
|
|
|
|
$database->options['comments'] = 1;
|
|
$database->save();
|
|
}
|
|
|
|
/**
|
|
* @brief Language strings preloaded
|
|
*/
|
|
protected bool $langLoaded = FALSE;
|
|
|
|
/**
|
|
* Get database id
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get__id(): int
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
/**
|
|
* Get comment bump
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get__comment_bump(): int
|
|
{
|
|
if ( $this->comment_bump === 0 )
|
|
{
|
|
return static::BUMP_ON_EDIT;
|
|
}
|
|
else if ( $this->comment_bump === 1 )
|
|
{
|
|
return static::BUMP_ON_COMMENT;
|
|
}
|
|
else if ( $this->comment_bump === 2 )
|
|
{
|
|
return static::BUMP_ON_EDIT + static::BUMP_ON_COMMENT;
|
|
}
|
|
|
|
/* Still here? Use the default */
|
|
return static::BUMP_ON_COMMENT;
|
|
}
|
|
|
|
/**
|
|
* Get database name
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get__title(): string
|
|
{
|
|
return Member::loggedIn()->language()->addToStack('content_db_' . $this->id);
|
|
}
|
|
|
|
/**
|
|
* Get database description
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function get__description(): ?string
|
|
{
|
|
return Member::loggedIn()->language()->addToStack('content_db_' . $this->id . '_desc');
|
|
}
|
|
|
|
/**
|
|
* Get default category
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function get__default_category(): ?string
|
|
{
|
|
$categoryClass = '\IPS\cms\Categories' . $this->id;
|
|
/* @var $categoryClass Categories */
|
|
if ( $this->default_category )
|
|
{
|
|
try
|
|
{
|
|
$categoryClass::load( $this->default_category );
|
|
return $this->default_category;
|
|
}
|
|
catch( OutOfRangeException $e )
|
|
{
|
|
$this->default_category = NULL;
|
|
}
|
|
}
|
|
|
|
if ( ! $this->default_category )
|
|
{
|
|
$roots = $categoryClass::roots( NULL );
|
|
|
|
if ( ! count( $roots ) )
|
|
{
|
|
/* Create a category */
|
|
$category = new $categoryClass;
|
|
$category->database_id = $this->id;
|
|
|
|
$catTitle = array();
|
|
$catDesc = array();
|
|
|
|
foreach( Lang::languages() as $id => $lang )
|
|
{
|
|
$catTitle[ $id ] = $lang->get('content_database_noun_pu');
|
|
$catDesc[ $id ] = '';
|
|
}
|
|
|
|
$category->saveForm( $category->formatFormValues( array(
|
|
'category_name' => $catTitle,
|
|
'category_description' => $catDesc,
|
|
'category_parent_id' => 0,
|
|
'category_has_perms' => 0,
|
|
'category_show_records' => 1
|
|
) ) );
|
|
|
|
$perms = $category->permissions();
|
|
|
|
Db::i()->update( 'core_permission_index', array(
|
|
'perm_view' => '*',
|
|
'perm_2' => '*',
|
|
'perm_3' => '*'
|
|
), array( 'perm_id=?', $perms['perm_id']) );
|
|
|
|
$roots = $categoryClass::roots( NULL );
|
|
}
|
|
|
|
$category = array_shift( $roots );
|
|
|
|
$this->default_category = $category->id;
|
|
$this->save();
|
|
|
|
/* Update records */
|
|
Db::i()->update( 'cms_custom_database_' . $this->id, array( 'category_id' => $category->id ), array( 'category_id=0' ) );
|
|
}
|
|
|
|
return $this->default_category;
|
|
}
|
|
|
|
/**
|
|
* Get fixed field data
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_fixed_field_perms(): array
|
|
{
|
|
if ( ! is_array( $this->_data['fixed_field_perms'] ) )
|
|
{
|
|
$this->_data['fixed_field_perms'] = json_decode( (string) $this->_data['fixed_field_perms'], true );
|
|
}
|
|
|
|
if ( is_array( $this->_data['fixed_field_perms'] ) )
|
|
{
|
|
return $this->_data['fixed_field_perms'];
|
|
}
|
|
|
|
return array();
|
|
}
|
|
|
|
/**
|
|
* Set the "fixed field" field
|
|
*
|
|
* @param mixed $value
|
|
* @return void
|
|
*/
|
|
public function set_fixed_field_perms( mixed $value ) : void
|
|
{
|
|
$this->_data['fixed_field_perms'] = ( is_array( $value ) ? json_encode( $value ) : $value );
|
|
}
|
|
|
|
/**
|
|
* Get fixed field settings
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_fixed_field_settings(): array
|
|
{
|
|
if ( ! is_array( $this->_data['fixed_field_settings'] ) )
|
|
{
|
|
$this->_data['fixed_field_settings'] = json_decode( (string) $this->_data['fixed_field_settings'], true );
|
|
}
|
|
|
|
if ( is_array( $this->_data['fixed_field_settings'] ) )
|
|
{
|
|
return $this->_data['fixed_field_settings'];
|
|
}
|
|
|
|
return array();
|
|
}
|
|
|
|
/**
|
|
* Set the "fixed field" settings field
|
|
*
|
|
* @param mixed $value
|
|
* @return void
|
|
*/
|
|
public function set_fixed_field_settings( mixed $value ) : void
|
|
{
|
|
$this->_data['fixed_field_settings'] = ( is_array( $value ) ? json_encode( $value ) : $value );
|
|
}
|
|
|
|
/**
|
|
* @return Page|null
|
|
*/
|
|
public function get_page() : ?Page
|
|
{
|
|
if( $this->page_id )
|
|
{
|
|
try
|
|
{
|
|
return Page::load( $this->page_id );
|
|
}
|
|
catch( OutOfRangeException ){}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function get_allow_club_categories() : bool
|
|
{
|
|
if( Settings::i()->clubs )
|
|
{
|
|
return $this->_data['allow_club_categories'];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function get_display_settings() : array
|
|
{
|
|
$return = [];
|
|
if( isset( $this->_data['display_settings'] ) and $this->_data['display_settings'] )
|
|
{
|
|
$return = json_decode( $this->_data['display_settings'], true );
|
|
}
|
|
|
|
$default = array(
|
|
'index' => [ 'type' => 'all', 'layout' => 'featured', 'template' => null ],
|
|
'categories' => [ 'layout' => 'table', 'template' => null ],
|
|
'listing' => [ 'layout' => 'table', 'template' => null ],
|
|
'display' => [ 'layout' => 'custom', 'template' => 'display' ],
|
|
'form' => [ 'layout' => 'custom', 'template' => 'form' ]
|
|
);;
|
|
|
|
foreach( $default as $k => $v )
|
|
{
|
|
if( !isset( $return[ $k ] ) )
|
|
{
|
|
$return[ $k ] = $v;
|
|
}
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* @param array|null $val
|
|
* @return void
|
|
*/
|
|
public function set_display_settings( ?array $val ) : void
|
|
{
|
|
$this->_data['display_settings'] = ( is_array( $val ) and count( $val ) ) ? json_encode( $val ) : null;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function get_template_form() : string
|
|
{
|
|
return $this->display_settings['form']['template'] ?? 'form';
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function get_template_display() : string
|
|
{
|
|
return $this->display_settings['display']['template'] ?? 'display';
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function get_template_listing() : string
|
|
{
|
|
return ( $this->display_settings['listing']['layout'] == 'custom' and $this->display_settings['listing']['template'] ) ? $this->display_settings['listing']['template'] : 'listing';
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function get_template_categories() : string
|
|
{
|
|
return ( $this->display_settings['categories']['layout'] == 'custom' and $this->display_settings['categories']['template'] ) ? $this->display_settings['categories']['template'] : 'category_index';
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function get_template_featured() : string
|
|
{
|
|
if( $this->display_settings['index']['type'] != 'categories' )
|
|
{
|
|
return ( $this->display_settings['index']['layout'] == 'custom' and $this->display_settings['index']['template'] ) ? $this->display_settings['index']['template'] : 'category_articles';
|
|
}
|
|
|
|
return $this->template_categories;
|
|
}
|
|
|
|
/**
|
|
* Get the title of the page when using a database
|
|
*
|
|
* @return string
|
|
*/
|
|
public function pageTitle(): string
|
|
{
|
|
if ( $this->pageTitle === NULL )
|
|
{
|
|
if ( $this->use_as_page_title )
|
|
{
|
|
$this->pageTitle = $this->_title;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
$this->pageTitle = Page::load( $this->page_id )->getHtmlTitle();
|
|
}
|
|
catch( Exception $e ) { }
|
|
}
|
|
}
|
|
|
|
return $this->pageTitle;
|
|
}
|
|
|
|
/**
|
|
* Check permissions
|
|
*
|
|
* @param mixed $permission A key which has a value in static::$permissionMap['view'] matching a column ID in core_permission_index
|
|
* @param Group|Member|null $member The member or group to check (NULL for currently logged in member)
|
|
* @param bool $considerPostBeforeRegistering If TRUE, and $member is a guest, will return TRUE if "Post Before Registering" feature is enabled
|
|
* @return bool
|
|
* @throws OutOfBoundsException If $permission does not exist in static::$permissionMap
|
|
*/
|
|
public function can( mixed $permission, Group|Member $member=NULL, bool $considerPostBeforeRegistering = TRUE ): bool
|
|
{
|
|
/* If we're looking from the front, make sure the database page also passes */
|
|
if ( $permission === 'view' and Dispatcher::hasInstance() and Dispatcher::i()->controllerLocation === 'front' and $this->page_id )
|
|
{
|
|
try
|
|
{
|
|
return parent::can( 'view', $member, $considerPostBeforeRegistering ) AND Page::load( $this->page_id )->can( 'view', $member, $considerPostBeforeRegistering );
|
|
}
|
|
catch( OutOfRangeException $ex )
|
|
{
|
|
return parent::can( 'view', $member, $considerPostBeforeRegistering );
|
|
}
|
|
}
|
|
|
|
return parent::can( $permission, $member, $considerPostBeforeRegistering );
|
|
}
|
|
|
|
/**
|
|
* Sets up and preloads some words
|
|
*
|
|
* @return void
|
|
*/
|
|
public function preLoadWords() : void
|
|
{
|
|
/* Skip this during installation / uninstallation as the words won't be loaded */
|
|
if ( !Dispatcher::hasInstance() or Dispatcher::i()->controllerLocation === 'setup' OR ( Dispatcher::i()->controllerLocation === 'admin' and Dispatcher::i()->module and Dispatcher::i()->module->key === 'applications' ) )
|
|
{
|
|
$this->langLoaded = TRUE;
|
|
return;
|
|
}
|
|
|
|
if ( ! $this->langLoaded )
|
|
{
|
|
if ( Dispatcher::i()->controllerLocation === 'admin' )
|
|
{
|
|
/* Moderator tools */
|
|
Member::loggedIn()->language()->words['modperms__core_Content_cms_Records' . $this->id ] = $this->_title;
|
|
Member::loggedIn()->language()->words['cms' . $this->id ] = Member::loggedIn()->language()->addToStack('categories');
|
|
|
|
/* Editor Areas */
|
|
Member::loggedIn()->language()->words['editor__cms_Records' . $this->id ] = $this->_title;
|
|
|
|
foreach( array( 'pin', 'unpin', 'feature', 'unfeature', 'edit', 'hide', 'unhide', 'view_hidden', 'future_publish', 'view_future', 'move', 'lock', 'unlock', 'reply_to_locked', 'delete', 'feature_comments', 'unfeature_comments', 'add_item_message', 'edit_item_message', 'delete_item_message', 'view_reports', 'assign' ) as $lang )
|
|
{
|
|
Member::loggedIn()->language()->words['can_' . $lang . '_content_db_lang_sl_' . $this->id ] = Member::loggedIn()->language()->addToStack( 'can_' . $lang . '_record', FALSE, array( 'sprintf' => array( $this->recordWord(1) ) ) );
|
|
Member::loggedIn()->language()->words['can_' . $lang . '_content_db_lang_su_' . $this->id ] = Member::loggedIn()->language()->addToStack( 'can_' . $lang . '_record', FALSE, array( 'sprintf' => array( $this->recordWord(1, TRUE) ) ) );
|
|
|
|
if ( in_array( $lang, array( 'edit', 'hide', 'unhide', 'view_hidden', 'delete' ) ) )
|
|
{
|
|
Member::loggedIn()->language()->words['can_' . $lang . '_content_record_comments_title_' . $this->id ] = Member::loggedIn()->language()->addToStack( 'can_' . $lang . '_rcomment', FALSE, array( 'sprintf' => array( $this->recordWord(1) ) ) );
|
|
Member::loggedIn()->language()->words['can_' . $lang . '_content_record_reviews_title_' . $this->id ] = Member::loggedIn()->language()->addToStack( 'can_' . $lang . '_rreview', FALSE, array( 'sprintf' => array( $this->recordWord(1) ) ) );
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->langLoaded = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* "Records" / "Record" word
|
|
*
|
|
* @param int $number Number
|
|
* @param bool $upper ucfirst string
|
|
* @return string
|
|
*/
|
|
public function recordWord( int $number = 2, bool $upper=FALSE ): string
|
|
{
|
|
if ( Application::appIsEnabled('cms') )
|
|
{
|
|
return Member::loggedIn()->language()->recordWord( $number, $upper, $this->id );
|
|
}
|
|
else
|
|
{
|
|
/* If the Pages app is disabled, just load a generic phrase */
|
|
$key = "content_database_noun_" . ( $number > 1 ? "p" : "s" ) . ( $upper ? "u" : "l" );
|
|
return Member::loggedIn()->language()->addToStack( $key );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* [ActiveRecord] Save Record
|
|
*
|
|
* @return void
|
|
*/
|
|
public function save(): void
|
|
{
|
|
/* If we are enabling search, we will need to index that content */
|
|
$rebuildSearchIndex = ( !$this->_new AND isset( $this->changed['search'] ) AND $this->changed['search'] );
|
|
$removeSearchIndex = ( !$this->_new AND isset( $this->changed['search'] ) AND !$this->changed['search'] );
|
|
|
|
parent::save();
|
|
|
|
/* If this database isn't searchable, make sure its content is not in the search index */
|
|
if( $removeSearchIndex )
|
|
{
|
|
Index::i()->removeClassFromSearchIndex( 'IPS\cms\Records' . $this->id );
|
|
Index::i()->removeClassFromSearchIndex( 'IPS\cms\Records\Comment' . $this->id );
|
|
Index::i()->removeClassFromSearchIndex( 'IPS\cms\Records\Review' . $this->id );
|
|
|
|
/* If there are any bg tasks to rebuild index, clear them */
|
|
foreach( Db::i()->select( '*', 'core_queue', array( '`key`=?', 'RebuildSearchIndex' ) ) as $queue )
|
|
{
|
|
$details = json_decode( $queue['data'], true );
|
|
|
|
if( isset( $details['class'] ) AND $details['class'] == 'IPS\cms\Records' . $this->id )
|
|
{
|
|
Db::i()->delete( 'core_queue', array( 'id=?', $queue['id'] ) );
|
|
}
|
|
}
|
|
}
|
|
elseif( $rebuildSearchIndex )
|
|
{
|
|
Task::queue( 'core', 'RebuildSearchIndex', array( 'class' => 'IPS\cms\Records' . $this->id ), 5, TRUE );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* [ActiveRecord] Delete Record
|
|
*
|
|
* @return void
|
|
*/
|
|
public function delete(): void
|
|
{
|
|
$fieldsClass = '\IPS\cms\Fields' . $this->id;
|
|
|
|
$class = '\IPS\cms\Categories' . $this->id;
|
|
/* @var $class Categories */
|
|
/* @var $fieldsClass Fields */
|
|
foreach( new ActiveRecordIterator( Db::i()->select( '*', 'cms_database_categories', array( 'category_database_id=?', $this->id ) ), '\IPS\cms\Categories' . $this->id ) as $cat )
|
|
{
|
|
$cat->delete();
|
|
}
|
|
|
|
$fileFields = array( 'record_image', 'record_image_thumb' );
|
|
$isMultiple = false;
|
|
|
|
foreach( $fieldsClass::roots( NULL ) as $id => $field )
|
|
{
|
|
$field->delete( TRUE );
|
|
|
|
if( $field->type == 'Upload' )
|
|
{
|
|
$fileFields[] = 'field_' . $field->id;
|
|
|
|
/* Delete thumbnails */
|
|
Task::queue( 'core', 'FileCleanup', array(
|
|
'table' => 'cms_database_fields_thumbnails',
|
|
'column' => 'thumb_location',
|
|
'storageExtension' => 'cms_Records',
|
|
'where' => array( array( 'thumb_field_id=?', $field->id ) ),
|
|
'deleteRows' => TRUE
|
|
), 4 );
|
|
|
|
if( $field->is_multiple )
|
|
{
|
|
$isMultiple = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Delete comments */
|
|
Db::i()->delete( 'cms_database_comments', array( 'comment_database_id=?', $this->id ) );
|
|
Db::i()->delete( 'cms_database_reviews', array( 'review_database_id=?', $this->id ) );
|
|
|
|
/* Delete from view counter */
|
|
Db::i()->delete( 'core_view_updates', array( 'classname=? ', 'IPS\cms\Records' . $this->id ) );
|
|
|
|
/* Delete records */
|
|
Task::queue( 'core', 'FileCleanup', array(
|
|
'table' => 'cms_custom_database_' . $this->id,
|
|
'column' => $fileFields,
|
|
'storageExtension' => 'cms_Records',
|
|
'primaryId' => 'primary_id_field',
|
|
'dropTable' => 'cms_custom_database_' . $this->id,
|
|
'multipleFiles' => $isMultiple
|
|
), 4 );
|
|
|
|
/* Delete revisions */
|
|
Db::i()->delete( 'cms_database_revisions', array( 'revision_database_id=?', $this->id ) );
|
|
|
|
/* Remove any reciprocal linking */
|
|
Db::i()->delete( 'cms_database_fields_reciprocal_map', array( 'map_foreign_database_id=? or map_origin_database_id=?', $this->id, $this->id ) );
|
|
|
|
/* Remove any fields in other databases associated to this DB */
|
|
foreach( Db::i()->select( '*', 'cms_database_fields', array( 'field_type=?', 'Item' ) ) as $fieldData )
|
|
{
|
|
$fieldClass = '\IPS\cms\Fields' . $fieldData['field_database_id'];
|
|
/* @var $fieldClass Fields */
|
|
if ($field = $fieldClass::load($fieldData['field_id']) AND isset( $field->extra['database'] ) and $field->extra['database'] AND $field->extra['database'] == $this->id )
|
|
{
|
|
$field->delete();
|
|
}
|
|
}
|
|
|
|
/* Delete notifications */
|
|
$memberIds = array();
|
|
|
|
foreach( Db::i()->select( '`member`', 'core_notifications', array( 'item_class=? ', 'IPS\cms\Records' . $this->id ) ) as $member )
|
|
{
|
|
$memberIds[ $member ] = $member;
|
|
}
|
|
|
|
Db::i()->delete( 'core_notifications', array( 'item_class=? ', 'IPS\cms\Records' . $this->id ) );
|
|
Db::i()->delete( 'core_follow', array( 'follow_app=? AND follow_area=?', 'cms', 'records' . $this->id ) );
|
|
|
|
/* Remove promoted content */
|
|
Db::i()->delete( 'core_content_promote', array( 'promote_class=?', 'IPS\cms\Records' . $this->id ) );
|
|
|
|
/* remove deletion log */
|
|
Db::i()->delete( 'core_deletion_log', array( 'dellog_content_class=?', 'IPS\cms\Records' . $this->id ) );
|
|
Db::i()->delete( 'core_deletion_log', array( 'dellog_content_class=?', 'IPS\cms\Records\Comment' . $this->id ) );
|
|
Db::i()->delete( 'core_deletion_log', array( 'dellog_content_class=?', 'IPS\cms\Records\Review' . $this->id ) );
|
|
|
|
/* remove metadata */
|
|
Db::i()->delete( 'core_content_meta', array( "meta_class=? ", 'IPS\cms\Records' . $this->id ) );
|
|
|
|
foreach( $memberIds as $member )
|
|
{
|
|
Member::load( $member )->recountNotifications();
|
|
}
|
|
|
|
/* Remove rss imports */
|
|
foreach( new ActiveRecordIterator( Db::i()->select( '*', 'core_rss_import', array( 'rss_import_class=?', 'IPS\cms\Records' . $this->id ) ), 'IPS\core\Rss\Import' ) as $import )
|
|
{
|
|
$import->delete();
|
|
}
|
|
|
|
/* Remove from search */
|
|
Index::i()->removeClassFromSearchIndex( 'IPS\cms\Records' . $this->id );
|
|
Index::i()->removeClassFromSearchIndex( 'IPS\cms\Records\Comment' . $this->id );
|
|
Index::i()->removeClassFromSearchIndex( 'IPS\cms\Records\Review' . $this->id );
|
|
|
|
/* Delete custom languages */
|
|
Lang::deleteCustom( 'cms', "content_db_" . $this->id );
|
|
Lang::deleteCustom( 'cms', "content_db_" . $this->id . '_desc');
|
|
Lang::deleteCustom( 'cms', "content_db_lang_sl_" . $this->id );
|
|
Lang::deleteCustom( 'cms', "content_db_lang_pl_" . $this->id );
|
|
Lang::deleteCustom( 'cms', "content_db_lang_su_" . $this->id );
|
|
Lang::deleteCustom( 'cms', "content_db_lang_pu_" . $this->id );
|
|
Lang::deleteCustom( 'cms', "content_db_lang_ia_" . $this->id );
|
|
Lang::deleteCustom( 'cms', "content_db_lang_sl_" . $this->id . '_pl' );
|
|
Lang::deleteCustom( 'cms', "__indefart_content_db_lang_sl_" . $this->id );
|
|
Lang::deleteCustom( 'cms', "__defart_content_db_lang_sl_" . $this->id );
|
|
Lang::deleteCustom( 'cms', "cms_records" . $this->id . '_pl' );
|
|
Lang::deleteCustom( 'cms', "module__cms_records" . $this->id );
|
|
Lang::deleteCustom( 'cms', "digest_area_cms_records" . $this->id );
|
|
Lang::deleteCustom( 'cms', "digest_area_cms_categories" . $this->id );
|
|
|
|
/* Unclaim attachments */
|
|
File::unclaimAttachments( 'cms_Records', NULL, NULL, $this->id );
|
|
File::unclaimAttachments( 'cms_Records' . $this->id );
|
|
|
|
/* Remove widgets */
|
|
$this->removeWidgets();
|
|
|
|
/* Delete the page */
|
|
if( $page = $this->page )
|
|
{
|
|
$page->delete();
|
|
}
|
|
|
|
/* Delete the database record */
|
|
parent::delete();
|
|
}
|
|
|
|
/**
|
|
* Remove any database widgets
|
|
*
|
|
* @return void
|
|
*/
|
|
public function removeWidgets() : void
|
|
{
|
|
$databaseWidgets = array( 'Database', 'LatestArticles' );
|
|
|
|
foreach ( Db::i()->select( '*', 'cms_page_widget_areas' ) as $item )
|
|
{
|
|
if( $pageBlocks = json_decode( $item['area_tree'], true ) )
|
|
{
|
|
$update = false;
|
|
$area = new Area( $pageBlocks, $item['area_area'] );
|
|
foreach( $area->getAllWidgets() as $widget )
|
|
{
|
|
if( $widget['app'] == 'cms' and in_array( $widget['key'], $databaseWidgets ) and !empty( $widget['configuration']['database'] ) and $widget['configuration']['database'] == $this->_id )
|
|
{
|
|
$area->removeWidget( $widget['unique'] );
|
|
$update = true;
|
|
}
|
|
}
|
|
|
|
if( $update )
|
|
{
|
|
Db::i()->update( 'cms_page_widget_areas', array( 'area_tree' => json_encode( $area->toArray( true, false ) ) ), array( 'area_page_id=? and area_area=?', $item['area_page_id'], $item['area_area'] ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the permission index permissions
|
|
*
|
|
* @param array $insert Permission data to insert
|
|
* @return void
|
|
*/
|
|
public function setPermissions( array $insert ) : void
|
|
{
|
|
parent::setPermissions( $insert );
|
|
|
|
/* Clear cache */
|
|
unset( Store::i()->cms_databases );
|
|
|
|
/* Clone these permissions to all categories that do not have permissions */
|
|
$class = '\IPS\cms\Categories' . $this->id;
|
|
/* @var $class Categories */
|
|
foreach( $class::roots( NULL ) as $category )
|
|
{
|
|
$this->setPermssionsRecursively( $category );
|
|
}
|
|
|
|
/* Clone the view permissions to the page */
|
|
if( $page = $this->page )
|
|
{
|
|
$page->setPermissions([
|
|
'app' => $page::$permApp, 'perm_type' => $page::$permType, 'perm_type_id' => $page->_id, 'perm_view' => $insert['perm_view']
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively set permissions
|
|
*
|
|
* @param Categories $category Category object
|
|
* @return void
|
|
*/
|
|
protected function setPermssionsRecursively( Categories $category ) : void
|
|
{
|
|
if ( ! $category->has_perms )
|
|
{
|
|
$category->cloneDatabasePermissions();
|
|
}
|
|
|
|
foreach( $category->children() as $child )
|
|
{
|
|
$this->setPermssionsRecursively( $child );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the number of categories in this database
|
|
*
|
|
* @return int
|
|
*/
|
|
public function numberOfCategories() : int
|
|
{
|
|
static $count = null;
|
|
if ( $count === NULL )
|
|
{
|
|
$count = Db::i()->select( 'count(*)', 'cms_database_categories', array( 'category_database_id=?', $this->_id ) )->first();
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* Get the number of club categories in this database
|
|
*
|
|
* @return int
|
|
*/
|
|
public function numberOfClubCategories() : int
|
|
{
|
|
static $count = null;
|
|
if ( $count === NULL )
|
|
{
|
|
$count = Db::i()->select( 'count(*)', 'cms_database_categories', array( 'category_database_id=? AND category_club_id IS NOT NULL', $this->_id ) )->first();
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
|
|
/**
|
|
* Determines if any fields from other databases are crosslinking to items in this database via the Relational field
|
|
*
|
|
* @param int $databaseId The ID of the database
|
|
* @return boolean
|
|
*/
|
|
public static function hasReciprocalLinking( int $databaseId ): bool
|
|
{
|
|
if ( isset( Store::i()->database_reciprocal_links ) )
|
|
{
|
|
$values = Store::i()->database_reciprocal_links;
|
|
}
|
|
else
|
|
{
|
|
$values = array();
|
|
foreach( static::databases() as $database )
|
|
{
|
|
$fieldsClass = 'IPS\cms\Fields' . $database->_id;
|
|
/* @var $fieldsClass Fields */
|
|
foreach( $fieldsClass::data() as $field )
|
|
{
|
|
if ( $field->type === 'Item' )
|
|
{
|
|
$extra = $field->extra;
|
|
if ( ! empty( $extra['database'] ) )
|
|
{
|
|
$values[ $database->_id ][] = $extra;
|
|
}
|
|
}
|
|
}
|
|
|
|
Store::i()->database_reciprocal_links = $values;
|
|
}
|
|
}
|
|
|
|
if ( is_array( $values ) )
|
|
{
|
|
foreach( $values as $id => $fields )
|
|
{
|
|
foreach( $fields as $fieldid => $data )
|
|
{
|
|
if ( $data['database'] == $databaseId and !empty( $data['crosslink'] ) )
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Determines if any fields from other databases are linking to items in this database via the Relational field
|
|
*
|
|
* @param int $databaseId The ID of the database
|
|
* @return boolean
|
|
*/
|
|
public static function isUsedAsReciprocalField( int $databaseId ) : bool
|
|
{
|
|
if ( isset( Store::i()->database_reciprocal_links ) )
|
|
{
|
|
$values = Store::i()->database_reciprocal_links;
|
|
}
|
|
else
|
|
{
|
|
$values = array();
|
|
foreach( static::databases() as $database )
|
|
{
|
|
$fieldsClass = 'IPS\cms\Fields' . $database->_id;
|
|
/* @var $fieldsClass Fields */
|
|
foreach( $fieldsClass::data() as $field )
|
|
{
|
|
if ( $field->type === 'Item' )
|
|
{
|
|
$extra = $field->extra;
|
|
if ( ! empty( $extra['database'] ) )
|
|
{
|
|
$values[ $database->_id ][] = $extra;
|
|
}
|
|
}
|
|
}
|
|
|
|
Store::i()->database_reciprocal_links = $values;
|
|
}
|
|
}
|
|
|
|
if ( is_array( $values ) )
|
|
{
|
|
foreach( $values as $id => $fields )
|
|
{
|
|
foreach( $fields as $fieldid => $data )
|
|
{
|
|
if ( $data['database'] == $databaseId )
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Rebuild the reciprocal linking maps across all databases
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function rebuildReciprocalLinkMaps() : void
|
|
{
|
|
/* Ensure the SPL are loaded from /cms/Application.php as this may be called by a task or upgrade module */
|
|
Application::load('cms');
|
|
|
|
Db::i()->delete( 'cms_database_fields_reciprocal_map' );
|
|
|
|
foreach( static::databases() as $database )
|
|
{
|
|
$fieldsClass = 'IPS\cms\Fields' . $database->_id;
|
|
/* @var $fieldsClass Fields */
|
|
foreach( $fieldsClass::data() as $field )
|
|
{
|
|
if ( $field->type === 'Item' )
|
|
{
|
|
Task::queue( 'cms', 'RebuildReciprocalMaps', array( 'database' => $database->_id, 'field' => $field->id ), 2, array( 'field' ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the filter cookie for this category/database
|
|
*
|
|
* @param Categories|null $category
|
|
* @return array|null
|
|
*/
|
|
public function getFilterCookie( ?Categories $category=null ): ?array
|
|
{
|
|
$key = $category ? $category->id : 'd' . $this->id;
|
|
if ( isset( Request::i()->cookie['cms_filters'] ) )
|
|
{
|
|
$saved = json_decode( Request::i()->cookie['cms_filters'], TRUE );
|
|
|
|
if ( array_key_exists( $key, $saved ) and count( $saved[ $key ] ) )
|
|
{
|
|
return $saved[ $key ];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Save filter cookie for this category
|
|
*
|
|
* @param array|bool $values Filter values to save (array) or FALSE to remove cookie
|
|
* @param Categories|null $category
|
|
* @return void
|
|
*/
|
|
public function saveFilterCookie( array|bool $values, ?Categories $category=null ): void
|
|
{
|
|
$key = $category ? $category->id : 'd' . $this->id;
|
|
$cookie = ( isset( Request::i()->cookie['cms_filters'] ) ) ? json_decode( Request::i()->cookie['cms_filters'], TRUE ) : array();
|
|
|
|
if ( $values === FALSE )
|
|
{
|
|
if ( array_key_exists( $key, $cookie ) )
|
|
{
|
|
unset( $cookie[ $key ] );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* We only want to include ones where we have actually specified values to filter on */
|
|
$toSave = array();
|
|
foreach( $values AS $k => $v )
|
|
{
|
|
if ( is_numeric( $k ) or $k == 'cms_record_i_started' )
|
|
{
|
|
$toSave[ $k ] = $v;
|
|
}
|
|
}
|
|
|
|
$cookie[ $key ] = $toSave;
|
|
}
|
|
|
|
Request::i()->setCookie( 'cms_filters', json_encode( $cookie ), DateTime::create()->add( new DateInterval( 'P7D' ) ) );
|
|
}
|
|
|
|
/**
|
|
* Use the cookie to build filters
|
|
*
|
|
* @param array $cookie
|
|
* @param Categories|null $category
|
|
* @return array
|
|
*/
|
|
public function buildWhereFromCookie( array $cookie, ?Categories $category=null ) : array
|
|
{
|
|
$where = [];
|
|
|
|
/** @var Fields $fieldClass */
|
|
$fieldClass = 'IPS\cms\Fields' . $this->id;
|
|
$customFields = $fieldClass::data( 'view', $category, $fieldClass::FIELD_SKIP_TITLE_CONTENT );
|
|
|
|
foreach( $cookie as $f => $v )
|
|
{
|
|
if ( $f == 'cms_record_i_started' and Member::loggedIn()->member_id )
|
|
{
|
|
$where[] = array( 'cms_custom_database_' . $this->id . '.member_id=' . Member::loggedIn()->member_id );
|
|
continue;
|
|
}
|
|
|
|
$k = 'content_field_' . $f;
|
|
|
|
if ( $customFields[ $f ]->type === 'Member' )
|
|
{
|
|
if ( ! empty( $v ) )
|
|
{
|
|
if ( is_array( $v ) )
|
|
{
|
|
foreach( $v as $m )
|
|
{
|
|
$member = Member::load( $m );
|
|
$where[] = [ "FIND_IN_SET( " . $member->member_id . ", REPLACE(field_" . $f . ", '\n',','))" ];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$member = Member::load( $v );
|
|
$where[] = [ "FIND_IN_SET( " . $member->member_id . ", REPLACE(field_" . $f . ", '\n',','))" ];
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if ( isset( $customFields[ $f ] ) and $v !== '___any___' )
|
|
{
|
|
if ( is_array( $v ) )
|
|
{
|
|
if ( array_key_exists( 'start', $v ) or array_key_exists( 'end', $v ) )
|
|
{
|
|
$start = ( $v['start'] instanceof DateTime ) ? $v['start']->getTimestamp() : intval( $v['start'] );
|
|
$end = ( $v['end'] instanceof DateTime ) ? $v['end']->getTimestamp() : intval( $v['end'] );
|
|
|
|
if ( $start or $end )
|
|
{
|
|
$where[] = array( '( ' . mb_substr( $k, 8 ) . ' BETWEEN ' . $start . ' AND ' . $end . ' )' );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$like = array();
|
|
foreach( $v as $val )
|
|
{
|
|
if ( $val === 0 or ! empty( $val ) )
|
|
{
|
|
$like[] = $val;
|
|
}
|
|
}
|
|
|
|
if( $customFields[ $f ]->default_value and in_array( $customFields[ $f ]->default_value, $v ) )
|
|
{
|
|
$where[] = array( "( " . mb_substr( $k, 8 ) . " IS NULL OR " . Db::i()->findInSet( mb_substr( $k, 8 ), $like ) . ")" );
|
|
}
|
|
else
|
|
{
|
|
$where[] = array( Db::i()->findInSet( mb_substr( $k, 8 ), $like ) );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( is_bool( $v ) )
|
|
{
|
|
/* YesNo fields are false or true */
|
|
if ( $v === false )
|
|
{
|
|
$where[] = array( '(' . mb_substr( $k, 8 ) . ' IS NULL or ' . mb_substr( $k, 8 ) . '=0)' );
|
|
}
|
|
else
|
|
{
|
|
$where[] = array( mb_substr( $k, 8 ) . "=1" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( $v !== 0 and ! $v )
|
|
{
|
|
$where[] = array( mb_substr( $k, 8 ) . " IS NULL" );
|
|
}
|
|
else
|
|
{
|
|
$where[] = array( mb_substr( $k, 8 ) . "=?", $v );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $where;
|
|
}
|
|
|
|
/**
|
|
* Get output for API
|
|
*
|
|
* @param Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
|
|
* @return array
|
|
* @apiresponse int id ID number
|
|
* @apiresponse string name Name
|
|
* @apiresponse bool useCategories If this database uses categories
|
|
* @clientapiresponse [\IPS\cms\Fields] fields The fields
|
|
* @apiresponse string url URL
|
|
* @clientapiresponse object|null permissions Node permissions
|
|
*/
|
|
public function apiOutput( Member $authorizedMember = NULL ): array
|
|
{
|
|
$return = array(
|
|
'id' => $this->id,
|
|
'name' => $this->_title,
|
|
'useCategories' => (bool) $this->use_categories,
|
|
);
|
|
|
|
if ( $authorizedMember === NULL )
|
|
{
|
|
$return['fields'] = array();
|
|
$fieldsClass = '\IPS\cms\Fields' . $this->id;
|
|
/* @var $fieldsClass Fields */
|
|
foreach ( $fieldsClass::roots() as $field )
|
|
{
|
|
$return['fields'][] = $field->apiOutput( $authorizedMember );
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
$pagePath = Page::loadByDatabaseId( $this->id )->full_path;
|
|
$return['url'] = (string) Url::internal( "app=cms&module=pages&controller=page&path=" . $pagePath, 'front', 'content_page_path' );
|
|
}
|
|
catch( OutOfRangeException $ex )
|
|
{
|
|
$return['url'] = NULL;
|
|
}
|
|
|
|
if( $authorizedMember === NULL )
|
|
{
|
|
$return['permissions'] = in_array( 'IPS\Node\Permissions', class_implements( get_class( $this ) ) ) ? $this->permissions() : NULL;
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
} |