Version 4.6.0
This commit is contained in:
1 parent
f79dcf067a
commit
517a5e1f70
2036 files changed
+110041
-26162
No files matched your search
@@ -28,6 +28,11 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
|
||||
* @brief Callback to retrieve results
|
||||
*/
|
||||
public $callback;
|
||||
|
||||
/**
|
||||
* @brief Custom form
|
||||
*/
|
||||
public $customFiltersForm = FALSE;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -103,7 +108,7 @@ class _Callback extends \IPS\Helpers\Chart\Dynamic
|
||||
* @param array $data The data
|
||||
* @return void
|
||||
*/
|
||||
public function compileForOutput(): void
|
||||
public function compileForOutput()
|
||||
{
|
||||
/* Init data */
|
||||
$data = $this->initData();
|
||||
|
||||
@@ -93,24 +93,26 @@ class _Chart
|
||||
$values = array_values( $values );
|
||||
foreach ( $this->headers as $data )
|
||||
{
|
||||
$value = \is_array( $values[ $i ] ) ? $values[ $i ]['value'] : $values[ $i ];
|
||||
|
||||
switch ( $data['type'] )
|
||||
{
|
||||
case 'string':
|
||||
if ( !\is_string( $values[ $i ] ) )
|
||||
if ( !\is_string( $value ) )
|
||||
{
|
||||
throw new \InvalidArgumentException( "VALUE_{$i}_NOT_STRING" );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
if ( !\is_numeric( $values[ $i ] ) and !\is_null( $values[ $i ] ) )
|
||||
if ( !\is_numeric( $value ) and !\is_null( $value ) )
|
||||
{
|
||||
throw new \InvalidArgumentException( "VALUE_{$i}_NOT_NUMBER" );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'bool':
|
||||
if ( !\is_bool( $values[ $i ] ) )
|
||||
if ( !\is_bool( $value ) )
|
||||
{
|
||||
throw new \InvalidArgumentException( "VALUE_{$i}_NOT_BOOL" );
|
||||
}
|
||||
@@ -119,12 +121,19 @@ class _Chart
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
case 'timeofday':
|
||||
if ( !( $values[ $i ] instanceof \IPS\DateTime ) )
|
||||
if ( !( $value instanceof \IPS\DateTime ) )
|
||||
{
|
||||
throw new \InvalidArgumentException( "VALUE_{$i}_NOT_DATETIME" );
|
||||
}
|
||||
|
||||
$values[ $i ] = $values[ $i ]->rfc3339();
|
||||
|
||||
if ( \is_array( $values[ $i ] ) )
|
||||
{
|
||||
$values[ $i ]['value'] = $value->rfc3339();
|
||||
}
|
||||
else
|
||||
{
|
||||
$values[ $i ] = $value->rfc3339();
|
||||
}
|
||||
break;
|
||||
}
|
||||
$i++;
|
||||
|
||||
@@ -61,7 +61,12 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
* @brief Table Column Formatters
|
||||
*/
|
||||
public $tableParsers = array();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Custom form
|
||||
*/
|
||||
public $customFiltersForm = FALSE;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -96,7 +101,7 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
/**
|
||||
* Add Series
|
||||
*
|
||||
* @param string $name Name
|
||||
* @param mixed $name Either a string with the series name or an array [ 'value' => "Series Name", 'key' => "XX" ]. Keys are used to give a country code for GeoCharts.
|
||||
* @param string $type Type of value
|
||||
* @li string
|
||||
* @li number
|
||||
@@ -117,6 +122,7 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
}
|
||||
else
|
||||
{
|
||||
$name = \is_array( $name ) ? $name['value'] : $name;
|
||||
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $name );
|
||||
$filterKey = $name;
|
||||
}
|
||||
@@ -125,7 +131,7 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
{
|
||||
if ( $this->type !== 'PieChart' and $this->type !== 'GeoChart' )
|
||||
{
|
||||
$this->addHeader( $name, $type );
|
||||
$this->addHeader( \is_array( $name ) ? $name['value'] : $name, $type );
|
||||
}
|
||||
|
||||
if( $this->groupBy )
|
||||
@@ -152,17 +158,75 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Init the data array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function initData()
|
||||
{
|
||||
$data = parent::initData();
|
||||
|
||||
if ( $this->customFiltersForm )
|
||||
{
|
||||
if ( $values = $this->getCustomFiltersForm()->values() )
|
||||
{
|
||||
$whereFunction = $this->customFiltersForm['where'];
|
||||
$seriesFunction = $this->customFiltersForm['series'];
|
||||
|
||||
foreach( $values as $k => $v )
|
||||
{
|
||||
unset( $values[ $k ] );
|
||||
$values[ \mb_substr( $k, \mb_strlen( $this->identifier ) ) ] = $v;
|
||||
}
|
||||
|
||||
$this->groupBy = $this->customFiltersForm['groupBy'];
|
||||
$this->where = array( array( $whereFunction( $values ) ) );
|
||||
|
||||
foreach( $seriesFunction( $values ) as $series )
|
||||
{
|
||||
$this->addSeries( $series[0], $series[1], $series[2], $series[3], $series[4] );
|
||||
}
|
||||
}
|
||||
else if ( $this->savedCustomFilters and \count( $this->savedCustomFilters ) )
|
||||
{
|
||||
$whereFunction = $this->customFiltersForm['where'];
|
||||
$seriesFunction = $this->customFiltersForm['series'];
|
||||
|
||||
$this->groupBy = $this->customFiltersForm['groupBy'];
|
||||
$this->where = array( array( $whereFunction( $this->savedCustomFilters ) ) );
|
||||
|
||||
foreach( $seriesFunction( $this->savedCustomFilters ) as $series )
|
||||
{
|
||||
$this->addSeries( $series[0], $series[1], $series[2], $series[3], $series[4] );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$seriesFunction = $this->customFiltersForm['defaultSeries'];
|
||||
|
||||
foreach( $seriesFunction() as $series )
|
||||
{
|
||||
$this->addSeries( $series[0], $series[1], $series[2], $series[3], $series[4] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Data for Output
|
||||
*
|
||||
* @param array $data The data
|
||||
* @return void
|
||||
*/
|
||||
public function compileForOutput(): void
|
||||
public function compileForOutput()
|
||||
{
|
||||
/* Init data */
|
||||
$data = $this->initData();
|
||||
|
||||
|
||||
/* Work out where clause */
|
||||
$where = $this->where;
|
||||
$where[] = array( "{$this->dateField}>?", 0 );
|
||||
@@ -237,8 +301,9 @@ class _Database extends \IPS\Helpers\Chart\Dynamic
|
||||
{
|
||||
$fromUnixTime = "CONVERT_TZ( {$fromUnixTime}, @@session.time_zone, '" . \IPS\Db::i()->escape_string( \IPS\Member::loggedIn()->timezone ) . "' )";
|
||||
}
|
||||
|
||||
$stmt = \IPS\Db::i()->select(
|
||||
"DATE_FORMAT( {$fromUnixTime}, '{$timescale}' ) AS time_as_alias, " . implode( ', ', array_unique( $this->series ) ) . ( $this->groupBy ? ", " . $this->groupBy : '' ),
|
||||
"SQL_BIG_RESULT DATE_FORMAT( {$fromUnixTime}, '{$timescale}' ) AS time_as_alias, " . implode( ', ', array_unique( $this->series ) ) . ( $this->groupBy ? ", " . $this->groupBy : '' ),
|
||||
$this->table,
|
||||
$where,
|
||||
'time_as_alias ASC',
|
||||
|
||||
@@ -126,6 +126,11 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
* @brief Error(s) to show on chart UI
|
||||
*/
|
||||
public $errors = array();
|
||||
|
||||
/**
|
||||
* @brief Saved custom filters
|
||||
*/
|
||||
public $savedCustomFilters = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -267,8 +272,8 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
$url = $url->setQueryString( 'end', array( $this->identifier => $this->end->getTimestamp() ) );
|
||||
}
|
||||
catch ( \Exception $e ) {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( isset( \IPS\Request::i()->filters[ $this->identifier ] ) )
|
||||
{
|
||||
$url = $url->setQueryString( 'filters', '' );
|
||||
@@ -299,10 +304,32 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
{
|
||||
if( $chart['chart_id'] == \IPS\Request::i()->chartId )
|
||||
{
|
||||
\IPS\Request::i()->filters = array( $this->identifier => json_decode( $chart['chart_configuration'], true ) );
|
||||
$filters = array();
|
||||
foreach( json_decode( $chart['chart_configuration'], true ) as $key => $value )
|
||||
{
|
||||
if ( \mb_substr( $key, 0, 11 ) == 'customform_' )
|
||||
{
|
||||
$this->savedCustomFilters[ \mb_substr( $key, 11 ) ] = $value;
|
||||
}
|
||||
else
|
||||
{
|
||||
$filters[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
\IPS\Request::i()->filters = array( $this->identifier => $filters );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach( \IPS\Request::i() as $key => $value )
|
||||
{
|
||||
if ( $value and \mb_substr( $key, 0, 11 ) === 'customform_' )
|
||||
{
|
||||
$name = \mb_substr( preg_replace( '#' . $this->identifier . '#', '', $key ), 11 );
|
||||
|
||||
$this->savedCustomFilters[ $name ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -311,7 +338,7 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
* @param array $data The data
|
||||
* @return void
|
||||
*/
|
||||
abstract public function compileForOutput(): void;
|
||||
abstract public function compileForOutput();
|
||||
|
||||
/**
|
||||
* Get the chart output
|
||||
@@ -335,8 +362,30 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
try
|
||||
{
|
||||
/* If we have filters, we can save them */
|
||||
if( \count( $this->availableFilters ) > 0 )
|
||||
if( \count( $this->availableFilters ) > 0 or $this->customFiltersForm )
|
||||
{
|
||||
/* Generate a form so we can save our filters as a new saved chart */
|
||||
$this->form = new \IPS\Helpers\Form;
|
||||
$this->form->class = 'ipsForm_vertical';
|
||||
|
||||
if ( $this->customFiltersForm )
|
||||
{
|
||||
if ( $customValues = $this->getCustomFiltersForm()->values( TRUE ) )
|
||||
{
|
||||
foreach( $customValues as $key => $value )
|
||||
{
|
||||
$this->form->hiddenValues['customform_' . $key ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have a sub selector from a node form, add on the identifier and pass off to the form to show the ajax */
|
||||
if ( \IPS\Request::i()->_nodeSelectName )
|
||||
{
|
||||
\IPS\Request::i()->_nodeSelectName = $this->identifier . \IPS\Request::i()->_nodeSelectName;
|
||||
return (string) $this->getCustomFiltersForm();
|
||||
}
|
||||
|
||||
/* We have an existing chart ID */
|
||||
if( isset( \IPS\Request::i()->chartId ) AND \IPS\Request::i()->chartId != '_default' )
|
||||
{
|
||||
@@ -350,10 +399,18 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate a form so we can save our filters as a new saved chart */
|
||||
$this->form = new \IPS\Helpers\Form;
|
||||
$this->form->class = 'ipsForm_vertical';
|
||||
|
||||
$custom = array();
|
||||
$chartFilters = ( isset( \IPS\Request::i()->chartFilters ) and \IPS\Request::i()->chartFilters ) ? \IPS\Request::i()->chartFilters : array();
|
||||
|
||||
foreach( \IPS\Request::i() as $key => $value )
|
||||
{
|
||||
if ( \mb_substr( $key, 0, 11 ) === 'customform_' )
|
||||
{
|
||||
$custom[ preg_replace( '#' . $this->identifier . '#', '', $key ) ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->form->add( new \IPS\Helpers\Form\Text( 'custom_chart_title', $title, TRUE ) );
|
||||
|
||||
if( $values = $this->form->values() )
|
||||
@@ -369,24 +426,32 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
/* And we want to save our filter updates */
|
||||
if( isset( \IPS\Request::i()->saveFilters ) )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_saved_charts', array( 'chart_configuration' => json_encode( \IPS\Request::i()->chartFilters ) ), array( 'chart_id=? AND chart_member=?', \IPS\Request::i()->chartId, \IPS\Member::loggedIn()->member_id ) );
|
||||
\IPS\Db::i()->update( 'core_saved_charts', array( 'chart_configuration' => json_encode( array_merge( $chartFilters, $custom ) ) ), array( 'chart_id=? AND chart_member=?', \IPS\Request::i()->chartId, \IPS\Member::loggedIn()->member_id ) );
|
||||
}
|
||||
}
|
||||
/* We are not viewing a saved chart */
|
||||
else
|
||||
{
|
||||
/* Generate a form so we can save our filters as a new saved chart */
|
||||
$this->form = new \IPS\Helpers\Form;
|
||||
$this->form->class = 'ipsForm_vertical';
|
||||
$this->form->add( new \IPS\Helpers\Form\Text( 'custom_chart_title', NULL, TRUE ) );
|
||||
|
||||
|
||||
if( $values = $this->form->values() )
|
||||
{
|
||||
$custom = array();
|
||||
foreach( \IPS\Request::i() as $key => $value )
|
||||
{
|
||||
if ( \mb_substr( $key, 0, 11 ) === 'customform_' )
|
||||
{
|
||||
$custom[ preg_replace( '#' . $this->identifier . '#', '', $key ) ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$chartFilters = ( isset( \IPS\Request::i()->chartFilters ) and \IPS\Request::i()->chartFilters ) ? \IPS\Request::i()->chartFilters : array();
|
||||
|
||||
/* Store the new chart */
|
||||
$id = \IPS\Db::i()->insert( 'core_saved_charts', array(
|
||||
'chart_member' => \IPS\Member::loggedIn()->member_id,
|
||||
'chart_controller' => \IPS\Request::i()->app . '_' . \IPS\Request::i()->module . '_' . \IPS\Request::i()->controller . ( \IPS\Request::i()->tab ? '_' . \IPS\Request::i()->tab : '' ),
|
||||
'chart_configuration' => json_encode( \IPS\Request::i()->chartFilters ),
|
||||
'chart_configuration' => json_encode( array_merge( $chartFilters, $custom ) ),
|
||||
'chart_title' => $values['custom_chart_title'],
|
||||
) );
|
||||
|
||||
@@ -419,7 +484,7 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
|
||||
/* Get data */
|
||||
$output = '';
|
||||
if ( !empty( $this->series ) )
|
||||
if ( !empty( $this->series ) or $this->customFiltersForm )
|
||||
{
|
||||
$output = $this->getOutput();
|
||||
}
|
||||
@@ -437,7 +502,7 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
{
|
||||
$chartOutput = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->dynamicChart( $this, $output );
|
||||
|
||||
if( \count( $this->availableFilters ) > 0 AND ( !\IPS\Request::i()->isAjax() OR ( \IPS\Request::i()->tab AND !\IPS\Request::i()->chartId ) ) )
|
||||
if( ( \count( $this->availableFilters ) > 0 or $this->customFiltersForm ) AND ( !\IPS\Request::i()->isAjax() OR ( \IPS\Request::i()->tab AND !\IPS\Request::i()->chartId ) ) )
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'global', 'core' )->tabs( $this->getChartTabs(), ( isset( \IPS\Request::i()->chartId ) ) ? \IPS\Request::i()->chartId : NULL, $chartOutput, $this->url, 'chartId', 'ipsTabs_small ipsTabs_contained' );
|
||||
}
|
||||
@@ -469,7 +534,7 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
*/
|
||||
protected function getChartTabs()
|
||||
{
|
||||
$tabs = array( '' => 'dynamic_chart_overview' );
|
||||
$tabs = array( '_default' => 'dynamic_chart_overview' );
|
||||
|
||||
foreach( $this->loadAvailableChartTabs() as $chart )
|
||||
{
|
||||
@@ -564,6 +629,35 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom filter form
|
||||
*
|
||||
* @return \IPS\Helpers\Form
|
||||
*/
|
||||
public function getCustomFiltersForm()
|
||||
{
|
||||
$customForm = new \IPS\Helpers\Form('filter_form', 'chart_customfilters_save');
|
||||
$customForm->class = 'ipsForm_vertical';
|
||||
|
||||
if ( isset( \IPS\Request::i()->chartId ) and \IPS\Request::i()->chartId != '_default' )
|
||||
{
|
||||
$customForm->hiddenValues['chartId'] = \IPS\Request::i()->chartId;
|
||||
}
|
||||
|
||||
foreach( $this->customFiltersForm['form'] as $field )
|
||||
{
|
||||
if ( ! preg_match( '#^' . $this->identifier . '#', $field->name ) )
|
||||
{
|
||||
$langKey = $field->name;
|
||||
$field->name = $this->identifier . $field->name;
|
||||
\IPS\Member::loggedIn()->language()->words[ $field->name ] = \IPS\Member::loggedIn()->language()->get( $langKey );
|
||||
}
|
||||
$customForm->add( $field );
|
||||
}
|
||||
|
||||
return $customForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download as CSV
|
||||
*
|
||||
@@ -572,7 +666,7 @@ abstract class _Dynamic extends \IPS\Helpers\Chart
|
||||
* @param string|NULL $fileName File Name, or NULL to use the title defined by this object.
|
||||
* @return void
|
||||
*/
|
||||
public function download( ?array $rawHeaders = NULL, ?array $rawRows = NULL, ?string $fileName = NULL ): void
|
||||
public function download( ?array $rawHeaders = NULL, ?array $rawRows = NULL, ?string $fileName = NULL )
|
||||
{
|
||||
/* Compile the data */
|
||||
$file = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
|
||||
|
||||
@@ -147,6 +147,6 @@ abstract class _Controller extends \IPS\Dispatcher\Controller
|
||||
*/
|
||||
protected function _coverPhotoReturnUrl()
|
||||
{
|
||||
return \IPS\Request::i()->referrer() ?: \IPS\Request::i()->url()->stripQueryString( 'do' );
|
||||
return \IPS\Request::i()->referrer() ?: \IPS\Request::i()->url()->stripQueryString( array( 'do', 'csrfKey' ) );
|
||||
}
|
||||
}
|
||||
@@ -95,18 +95,7 @@ class _Address extends FormAbstract
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate default country
|
||||
*
|
||||
* @return string|NULL
|
||||
* @deprecated
|
||||
*/
|
||||
protected function _calculateDefaultCountry()
|
||||
{
|
||||
return static::calculateDefaultCountry();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Value
|
||||
*
|
||||
|
||||
@@ -27,6 +27,22 @@ class _Captcha extends FormAbstract
|
||||
*/
|
||||
protected $captcha = NULL;
|
||||
|
||||
/**
|
||||
* Does the configured CAPTCHA service support being added in a modal?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function supportsModal()
|
||||
{
|
||||
if ( \IPS\Settings::i()->bot_antispam_type === 'none' )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
$class = '\IPS\Helpers\Form\Captcha\\' . mb_ucfirst( \IPS\Settings::i()->bot_antispam_type );
|
||||
return ( !isset( $class::$supportsModal ) or $class::$supportsModal ); // isset() check is for backwards compatibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
||||
@@ -22,6 +22,11 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
class _Invisible
|
||||
{
|
||||
/**
|
||||
* Does this CAPTCHA service support being added in a modal?
|
||||
*/
|
||||
public static $supportsModal = TRUE;
|
||||
|
||||
/**
|
||||
* @brief Error
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,11 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
class _Keycaptcha
|
||||
{
|
||||
/**
|
||||
* Does this CAPTCHA service support being added in a modal?
|
||||
*/
|
||||
public static $supportsModal = FALSE;
|
||||
|
||||
/**
|
||||
* @brief Error
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,11 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
class _Recaptcha2
|
||||
{
|
||||
/**
|
||||
* Does this CAPTCHA service support being added in a modal?
|
||||
*/
|
||||
public static $supportsModal = TRUE;
|
||||
|
||||
/**
|
||||
* @brief Error
|
||||
*/
|
||||
|
||||
@@ -93,7 +93,7 @@ class _CheckboxSet extends Select
|
||||
$value = array( $value );
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->checkboxset( $this->name, $value, $this->required, $this->parseOptions(), $this->options['multiple'], $this->options['class'], $this->options['disabled'], $this->options['toggles'], NULL, $this->options['unlimited'], $this->options['unlimitedLang'], $this->options['unlimitedToggles'], $this->options['unlimitedToggleOn'], $descriptions );
|
||||
return \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->checkboxset( $this->name, $value, $this->required, $this->parseOptions(), $this->options['multiple'], $this->options['class'], $this->options['disabled'], $this->options['toggles'], NULL, $this->options['unlimited'], $this->options['unlimitedLang'], $this->options['unlimitedToggles'], $this->options['unlimitedToggleOn'], $descriptions, $this->options['impliedUnlimited'] );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,6 +113,12 @@ class _CheckboxSet extends Select
|
||||
/* We need the array keys. For custom fields we will have 0 => 1, 1 => 1 if checkboxes 0 and 1 are checked, while for Content we will have
|
||||
customKey => 1, customOtherKey => 1 - we always need the array keys */
|
||||
$value = \is_array( $value ) ? array_keys( $value ) : array();
|
||||
|
||||
if ( $this->options['unlimited'] !== NULL and $this->options['impliedUnlimited'] and \count( $value ) == \count( $this->options['options'] ) )
|
||||
{
|
||||
return $this->options['unlimited'];
|
||||
}
|
||||
|
||||
if ( $this->options['returnLabels'] )
|
||||
{
|
||||
if ( \is_array( $value ) )
|
||||
|
||||
@@ -34,6 +34,7 @@ class _Editor extends FormAbstract
|
||||
2,
|
||||
'foo'
|
||||
),
|
||||
'attachIdsLang' => NULL, // Language ID number if this Editor is part of a Translatable field.
|
||||
'minimize' => 'clickme', // Language string to use for minimized view. NULL will mean editor is not minimized.
|
||||
'minimizeIcon' => 'flag-us', // Icon to use for minimized view
|
||||
'allButtons' => FALSE, // Only used for the customisation ACP page. Do not use.
|
||||
@@ -53,6 +54,7 @@ class _Editor extends FormAbstract
|
||||
'key' => NULL,
|
||||
'autoSaveKey' => NULL,
|
||||
'attachIds' => NULL,
|
||||
'attachIdsLang' => NULL,
|
||||
'minimize' => NULL,
|
||||
'minimizeIcon' => 'fa fa-comment-o',
|
||||
'allButtons' => FALSE,
|
||||
@@ -400,7 +402,12 @@ class _Editor extends FormAbstract
|
||||
}
|
||||
else
|
||||
{
|
||||
$fileAttachments[] = \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->attachedFile( \IPS\Http\Url::baseUrl() . "applications/core/interface/file/attachment.php?id=" . $attachment['attach_id'], $attachment['attach_file'], FALSE, $attachment['attach_ext'], $attachment['attach_id'] );
|
||||
$url = \IPS\Http\Url::baseUrl() . "applications/core/interface/file/attachment.php?id=" . $attachment['attach_id'];
|
||||
if ( $attachment['attach_security_key'] )
|
||||
{
|
||||
$url .= "&key={$attachment['attach_security_key']}";
|
||||
}
|
||||
$fileAttachments[] = \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->attachedFile( $url, $attachment['attach_file'], FALSE, $attachment['attach_ext'], $attachment['attach_id'], $attachment['attach_security_key'] );
|
||||
}
|
||||
|
||||
if ( !isset( $parser ) or !\in_array( $attachment['attach_id'], $parser->mappedAttachments ) )
|
||||
@@ -411,7 +418,8 @@ class _Editor extends FormAbstract
|
||||
'id1' => ( \is_array( $this->options['attachIds'] ) and isset( $this->options['attachIds'][0] ) ) ? $this->options['attachIds'][0] : NULL,
|
||||
'id2' => ( \is_array( $this->options['attachIds'] ) and isset( $this->options['attachIds'][1] ) ) ? $this->options['attachIds'][1] : NULL,
|
||||
'id3' => ( \is_array( $this->options['attachIds'] ) and isset( $this->options['attachIds'][2] ) ) ? $this->options['attachIds'][2] : NULL,
|
||||
'temp' => \is_string( $this->options['attachIds'] ) ? $this->options['attachIds'] : ( $this->options['attachIds'] === NULL ? md5( $this->options['autoSaveKey'] ) : $this->options['attachIds'] )
|
||||
'temp' => \is_string( $this->options['attachIds'] ) ? $this->options['attachIds'] : ( $this->options['attachIds'] === NULL ? md5( $this->options['autoSaveKey'] ) : $this->options['attachIds'] ),
|
||||
'lang' => $this->options['attachIdsLang'],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -488,7 +496,7 @@ class _Editor extends FormAbstract
|
||||
*/
|
||||
protected function _getParser()
|
||||
{
|
||||
return new \IPS\Text\Parser( TRUE, ( $this->options['attachIds'] === NULL ? md5( $this->options['autoSaveKey'] ) : $this->options['attachIds'] ), NULL, "{$this->options['app']}_{$this->options['key']}", !$this->bypassFilterProfanity(), !$this->canUseHtml(), method_exists( $this->extension, 'htmlPurifierConfig' ) ? array( $this->extension, 'htmlPurifierConfig' ) : NULL );
|
||||
return new \IPS\Text\Parser( TRUE, ( $this->options['attachIds'] === NULL ? md5( $this->options['autoSaveKey'] ) : $this->options['attachIds'] ), NULL, "{$this->options['app']}_{$this->options['key']}", !$this->bypassFilterProfanity(), !$this->canUseHtml(), method_exists( $this->extension, 'htmlPurifierConfig' ) ? array( $this->extension, 'htmlPurifierConfig' ) : NULL, TRUE, $this->options['attachIdsLang'] );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -589,6 +597,7 @@ class _Editor extends FormAbstract
|
||||
/* Reset the original filename based on the attachment record as filenames can be renamed if they contain special characters as AWS being the lowest common denominator cannot
|
||||
handle special characters */
|
||||
$file->originalFilename = $attachment['attach_file'];
|
||||
$file->securityKey = $attachment['attach_security_key'];
|
||||
|
||||
$existing[ $attachment['attach_id'] ] = $file;
|
||||
|
||||
@@ -690,6 +699,10 @@ class _Editor extends FormAbstract
|
||||
$where[] = array( "id{$i}=?", $id );
|
||||
$i++;
|
||||
}
|
||||
if ( $this->options['attachIdsLang'] )
|
||||
{
|
||||
$where[] = array( "lang=?", $this->options['attachIdsLang'] );
|
||||
}
|
||||
|
||||
\IPS\Db::i()->delete( 'core_attachments_map', $where );
|
||||
}
|
||||
@@ -759,8 +772,44 @@ class _Editor extends FormAbstract
|
||||
{
|
||||
$where[] = array( "id{$i}=?", $id );
|
||||
$i++;
|
||||
}
|
||||
|
||||
/* If we only want ones for particular languages, filter them out. We don't do this in the WHERE clause for backwards compatibility */
|
||||
if ( $this->options['attachIdsLang'] )
|
||||
{
|
||||
$setToAllLangs = [];
|
||||
$_existingAttachments = [];
|
||||
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_attachments_map', $where ) as $existingAttachment )
|
||||
{
|
||||
if ( $existingAttachment['lang'] === NULL )
|
||||
{
|
||||
$existingAttachment['lang'] = $this->options['attachIdsLang'];
|
||||
if ( !\in_array( $existingAttachment['attachment_id'], $setToAllLangs ) )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_attachments_map', [ 'attachment_id=? AND location_key=? AND id1=? AND id2=? AND id3=? and lang IS NULL', $existingAttachment['attachment_id'], $existingAttachment['location_key'], $existingAttachment['id1'], $existingAttachment['id2'], $existingAttachment['id3'] ] );
|
||||
foreach ( \IPS\Lang::languages() as $lang )
|
||||
{
|
||||
$newRow = $existingAttachment;
|
||||
$newRow['lang'] = $lang->id;
|
||||
\IPS\Db::i()->insert( 'core_attachments_map', $newRow );
|
||||
}
|
||||
$setToAllLangs[] = $existingAttachment['attachment_id'];
|
||||
}
|
||||
}
|
||||
if ( $existingAttachment['lang'] == $this->options['attachIdsLang'] )
|
||||
{
|
||||
$_existingAttachments[] = $existingAttachment['attachment_id'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
$_existingAttachments = iterator_to_array( \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', $where ) );
|
||||
else
|
||||
{
|
||||
$_existingAttachments = iterator_to_array( \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', $where ) );
|
||||
}
|
||||
|
||||
if ( !empty( $_existingAttachments ) )
|
||||
{
|
||||
$existingAttachments = \IPS\Db::i()->in( 'attach_id', $_existingAttachments ) . ' OR ';
|
||||
@@ -836,11 +885,11 @@ class _Editor extends FormAbstract
|
||||
}
|
||||
elseif ( \IPS\Settings::i()->attach_allowed_types == 'media' )
|
||||
{
|
||||
return array_merge( \IPS\Image::$imageExtensions, \IPS\File::$videoExtensions );
|
||||
return array_merge( \IPS\Image::supportedExtensions(), \IPS\File::$videoExtensions );
|
||||
}
|
||||
elseif ( \IPS\Settings::i()->attach_allowed_types == 'images' )
|
||||
{
|
||||
return \IPS\Image::$imageExtensions;
|
||||
return \IPS\Image::supportedExtensions();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -506,19 +506,12 @@ class _Form
|
||||
$errorTabs = array();
|
||||
foreach ( $this->elements as $tab => $elements )
|
||||
{
|
||||
$html[ $tab ] = '';
|
||||
foreach ( $elements as $k => $element )
|
||||
{
|
||||
if ( $element instanceof Form\Matrix )
|
||||
{
|
||||
$html[ $tab ] .= \IPS\Theme::i()->getTemplate( 'forms', 'core' )->emptyRow( $element->nested(), $k );
|
||||
continue;
|
||||
}
|
||||
if ( !\is_string( $element ) and $element->error )
|
||||
if ( !( $element instanceof Form\Matrix ) and !\is_string( $element ) and $element->error )
|
||||
{
|
||||
$errorTabs[] = $tab;
|
||||
}
|
||||
$html[ $tab ] .= ( $element instanceof \IPS\Helpers\Form\FormAbstract ) ? $element->rowHtml( $this ) : (string) $element;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class _Item extends FormAbstract
|
||||
'class' => '\IPS\core\Foo', // The \Content\Item class
|
||||
'permissionCheck' => 'read', // If a permission key is provided, only content items that the member has that permission for will be available.
|
||||
'maxItems' => NULL, // Maximum items allowed to select, or NULL for unlimited
|
||||
'orderResults' => NULL|array // NULL or array( field, 'asc' ) where field is a mappable content item field (date, title, etc)
|
||||
'orderResults' => NULL|FALSE|array // NULL for default date ordering, FALSE for no ordering, or array( field, 'asc' ) where field is a mappable content item field (date, title, etc)
|
||||
'itemTemplate' => NULL // NULL or array of template group and template name array( \IPS\Theme::i()->getTemplate( 'group', 'app', 'location'), 'name' )
|
||||
'containerIds' => array // Array of container IDs to limit the search
|
||||
'minAjaxLength' => int // Number of characters to type before selector appears
|
||||
@@ -163,6 +163,19 @@ class _Item extends FormAbstract
|
||||
{
|
||||
$items[ $item->$idField ] = $item;
|
||||
}
|
||||
|
||||
/* If we didn't want to order results, reset the order of results as appropriate */
|
||||
if( $this->options['orderResults'] === FALSE )
|
||||
{
|
||||
$_sortedItems = array();
|
||||
|
||||
foreach( $this->value as $id )
|
||||
{
|
||||
$_sortedItems[ $id ] = $items[ $id ];
|
||||
}
|
||||
|
||||
$items = $_sortedItems;
|
||||
}
|
||||
}
|
||||
|
||||
$this->value = $items;
|
||||
|
||||
@@ -393,7 +393,7 @@ class _Matrix extends \IPS\Helpers\Form
|
||||
}
|
||||
|
||||
/* If the "check all" box was checked, set it to TRUE */
|
||||
if ( $element instanceof Checkbox and isset( \IPS\Request::i()->__all[ $columnName ] ) )
|
||||
if ( $element instanceof Checkbox and isset( \IPS\Request::i()->__all[ $columnName ] ) and ! $element->options['disabled'] )
|
||||
{
|
||||
$element->value = TRUE;
|
||||
}
|
||||
|
||||
@@ -403,7 +403,7 @@ class _Node extends FormAbstract
|
||||
* @param bool $noJS No-javascript fallback
|
||||
* @return void
|
||||
*/
|
||||
protected function _populate( &$nodes, &$disabled, $depth, $node, &$children, $depthLimit = NULL, $parent = NULL, $noJS = FALSE, $totalLimit = NULL, &$currentCount )
|
||||
protected function _populate( &$nodes, &$disabled, $depth, $node, &$children, $depthLimit = NULL, $parent = NULL, $noJS = FALSE, $totalLimit = NULL, &$currentCount = 0 )
|
||||
{
|
||||
$showable = $this->_getShowable();
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class _Password extends Text
|
||||
'strengthMember' => NULL,
|
||||
'strengthRequest' => array(),
|
||||
'htmlAutocomplete' => "current-password",
|
||||
|
||||
'enforceMaxLimit' => TRUE,
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -87,7 +87,7 @@ class _Password extends Text
|
||||
throw new \InvalidArgumentException( 'err_password_length' );
|
||||
}
|
||||
|
||||
if ( mb_strlen( $this->value ) > 72 )
|
||||
if ( $this->options['enforceMaxLimit'] and mb_strlen( $this->value ) > 72 )
|
||||
{
|
||||
throw new \InvalidArgumentException( 'err_password_toolong' );
|
||||
}
|
||||
@@ -149,7 +149,7 @@ class _Password extends Text
|
||||
|
||||
if ( !$valid )
|
||||
{
|
||||
throw new \InvalidArgumentException( 'login_err_bad_password' );
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_bad_password', FALSE, array( 'pluralize' => array( $method->authType() ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -170,6 +170,11 @@ EOF;
|
||||
/* Do all the questions have at least 2 options? */
|
||||
foreach ( $this->value->choices as $question )
|
||||
{
|
||||
/* Do we have a question title? */
|
||||
if ( empty( trim( $question['question'] ) ) )
|
||||
{
|
||||
throw new \InvalidArgumentException('form_poll_no_question_title');
|
||||
}
|
||||
if ( \count( $question['choice'] ) < 2 )
|
||||
{
|
||||
throw new \InvalidArgumentException('form_poll_too_few_answers');
|
||||
|
||||
@@ -41,6 +41,7 @@ class _Select extends FormAbstract
|
||||
'noDefault' => TRUE // For radios, you can set this to TRUE if you do not want any radio option selected by default (useful for things like polls)
|
||||
'returnLabels' => FALSE // If TRUE, will return the labels rather than the keys as the value
|
||||
'sort' => FALSE, // If TRUE, options will be sorted by JavaScript. Useful where the options are language-dependant but you still want them to be in alphabetical order
|
||||
'impliedUnlimited' => FALSE, // For checkbox sets, if 'unlimited' is set and this is FALSE a fancy toggler interface is presented. If TRUE, a checkbox set will be presented and the unlimited value will be implied if all options are checked.
|
||||
);
|
||||
* @endcode
|
||||
*/
|
||||
@@ -60,6 +61,7 @@ class _Select extends FormAbstract
|
||||
'noDefault' => FALSE,
|
||||
'returnLabels' => FALSE,
|
||||
'sort' => FALSE,
|
||||
'impliedUnlimited' => FALSE,
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -504,6 +504,10 @@ class _Text extends TextArea
|
||||
{
|
||||
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_not_allowed', FALSE, array( 'sprintf' => array( $section ) ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$newVal .= $section;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -73,7 +73,7 @@ class _Translatable extends FormAbstract
|
||||
{
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_sys_lang_words', array( 'word_key=?', $this->options['key'] ) )->setKeyField('lang_id') as $k => $v )
|
||||
{
|
||||
$v = $v['word_custom'] ?: $v['word_default'];
|
||||
$v = $v['word_custom'];
|
||||
if ( $v or !isset( $values[ $k ] ) )
|
||||
{
|
||||
if ( \is_array( $this->options['sprintf'] ) )
|
||||
@@ -118,7 +118,8 @@ class _Translatable extends FormAbstract
|
||||
foreach ( \IPS\Lang::getEnabledLanguages() as $lang )
|
||||
{
|
||||
$options = $this->options['editor'];
|
||||
$options['autoSaveKey'] .= $lang->id;
|
||||
$options['autoSaveKey'] .= $lang->id;
|
||||
$options['attachIdsLang'] = $lang->id;
|
||||
$this->editors[ $lang->id ] = new Editor( "{$this->name}[{$lang->id}]", isset( $this->value[ $lang->id ] ) ? $this->value[ $lang->id ] : NULL, $this->required, $options );
|
||||
}
|
||||
}
|
||||
@@ -153,7 +154,7 @@ class _Translatable extends FormAbstract
|
||||
$return = array();
|
||||
foreach ( $this->_getEditors() as $languageId => $editor )
|
||||
{
|
||||
$return[ $languageId ] = $editor->getValue();
|
||||
$return[ $languageId ] = $editor->value;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ class _Upload extends FormAbstract
|
||||
/* If this has to be an image, set the allowed file types */
|
||||
if ( isset( $options['image'] ) and !isset( $options['image']['optional'] ) and !isset( $options['allowedFileTypes'] ) )
|
||||
{
|
||||
$options['allowedFileTypes'] = \IPS\Image::$imageExtensions;
|
||||
$options['allowedFileTypes'] = \IPS\Image::supportedExtensions();
|
||||
}
|
||||
|
||||
/* Call parent constructor */
|
||||
@@ -213,9 +213,6 @@ class _Upload extends FormAbstract
|
||||
/* Chunked */
|
||||
if ( $storageClass::$supportsChunking and method_exists( $storageClass, 'chunkInit' ) and isset( \IPS\Request::i()->chunks ) and \IPS\Request::i()->chunks > 1 )
|
||||
{
|
||||
/* We need to be able to set data into sessions */
|
||||
\IPS\Request::i()->setCookie( 'noCache', 1 );
|
||||
|
||||
/* If this is the FIRST chunk, start the process */
|
||||
if ( \IPS\Request::i()->chunk == 0 )
|
||||
{
|
||||
@@ -259,16 +256,12 @@ class _Upload extends FormAbstract
|
||||
$image->setExifData( $fileObj->contents() );
|
||||
}
|
||||
|
||||
$hasBeenResized = FALSE;
|
||||
if( \is_array( $this->options['image'] ) )
|
||||
{
|
||||
$hasBeenResized = $image->resizeToMax( $this->options['image']['maxWidth'] ?: NULL, $this->options['image']['maxHeight'] ?: NULL );
|
||||
$image->resizeToMax( $this->options['image']['maxWidth'] ?: NULL, $this->options['image']['maxHeight'] ?: NULL );
|
||||
}
|
||||
|
||||
if ( $hasBeenResized or $image->hasBeenRotated )
|
||||
{
|
||||
$fileObj->replace( (string) $image );
|
||||
}
|
||||
$fileObj->replace( (string) $image );
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
@@ -298,6 +291,7 @@ class _Upload extends FormAbstract
|
||||
'imagesrc' => $fileObj->isImage() ? (string) $fileObj->url : NULL,
|
||||
'videosrc' => $fileObj->isVideo() ? (string) $fileObj->url : NULL,
|
||||
'thumbnail' => ( $fileObj->isImage() AND $fileObj->attachmentThumbnailUrl !== NULL and \is_string( $fileObj->attachmentThumbnailUrl ) ) ? \IPS\File::get( $this->options['storageExtension'], $fileObj->attachmentThumbnailUrl )->url : NULL,
|
||||
'securityKey' => $fileObj->securityKey
|
||||
) );
|
||||
}
|
||||
else
|
||||
@@ -316,6 +310,7 @@ class _Upload extends FormAbstract
|
||||
'imagesrc' => $fileObj->isImage() ? (string) $fileObj->url : NULL,
|
||||
'videosrc' => $fileObj->isVideo() ? (string) $fileObj->url : NULL,
|
||||
'thumbnail' => ( $fileObj->isImage() AND $fileObj->attachmentThumbnailUrl !== NULL and \is_string( $fileObj->attachmentThumbnailUrl ) ) ? \IPS\File::get( $this->options['storageExtension'], $fileObj->attachmentThumbnailUrl )->url : NULL,
|
||||
'securityKey' => $fileObj->securityKey
|
||||
) );
|
||||
}
|
||||
}
|
||||
@@ -446,6 +441,7 @@ class _Upload extends FormAbstract
|
||||
'size' => \IPS\Output\Plugin\Filesize::humanReadableFilesize( $file->filesize(), FALSE, TRUE ),
|
||||
'sizeRaw' => $file->filesize(),
|
||||
'default' => ( isset( $attachment ) AND $attachment['default'] ) ? $attachment['default'] : NULL,
|
||||
'securityKey' => ( isset( $attachment ) ) ? $attachment['attach_security_key'] : $file->securityKey
|
||||
);
|
||||
}
|
||||
catch( \UnderflowException $e ){}
|
||||
@@ -705,7 +701,7 @@ class _Upload extends FormAbstract
|
||||
$ext = mb_strtolower( mb_substr( $file['name'], ( mb_strrpos( $file['name'], '.' ) + 1 ) ) );
|
||||
|
||||
/* Don't allow "XSS" in images */
|
||||
if( \in_array( $ext, \IPS\File::$safeFileExtensions ) AND \in_array( $ext, \IPS\Image::$imageExtensions ) )
|
||||
if( \in_array( $ext, \IPS\File::$safeFileExtensions ) AND \in_array( $ext, \IPS\Image::supportedExtensions() ) )
|
||||
{
|
||||
if( \IPS\File::checkXssInFile( file_get_contents( $file['tmp_name'] ) ) )
|
||||
{
|
||||
@@ -727,26 +723,27 @@ class _Upload extends FormAbstract
|
||||
$ext = mb_strtolower( mb_substr( $filename, mb_strrpos( $filename, '.' ) + 1 ) );
|
||||
|
||||
/* Resize images */
|
||||
if ( \in_array( $ext, \IPS\Image::$imageExtensions ) AND ( \is_array( $options['image'] ) OR $options['checkImage'] ) )
|
||||
if ( \in_array( $ext, \IPS\Image::supportedExtensions() ) AND ( \is_array( $options['image'] ) OR $options['checkImage'] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$image = \IPS\Image::create( $contents );
|
||||
|
||||
$hasBeenResized = FALSE;
|
||||
|
||||
if( $image::exifSupported() )
|
||||
{
|
||||
$image->setExifData( $contents );
|
||||
}
|
||||
|
||||
if( \is_array( $options['image'] ) )
|
||||
{
|
||||
$image->resizeToMax( $options['image']['maxWidth'] ?: NULL, $options['image']['maxHeight'] ?: NULL );
|
||||
}
|
||||
|
||||
if ( $hasBeenResized or $image->hasBeenRotated )
|
||||
{
|
||||
return (string) $image;
|
||||
}
|
||||
|
||||
return (string) $image;
|
||||
}
|
||||
catch ( \Exception $e ) {}
|
||||
}
|
||||
|
||||
|
||||
return $contents;
|
||||
}, $this->options['storageContainer'], $this->options['obscure'] );
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ class _MultipleRedirect
|
||||
public function __construct( $url, $callback, $finished, $finalRedirect=TRUE )
|
||||
{
|
||||
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'global_core.js', 'core', 'global' ) );
|
||||
\IPS\Output::i()->bypassCsrfKeyCheck = TRUE;
|
||||
|
||||
$this->url = $url;
|
||||
$key = 'mr-' . md5( $url );
|
||||
@@ -148,7 +149,7 @@ class _MultipleRedirect
|
||||
public function __toString()
|
||||
{
|
||||
/* Don't cache for a short while to ensure sessions work */
|
||||
\IPS\Request::i()->setCookie( 'noCache', 1 );
|
||||
\IPS\Output::i()->pageCaching = FALSE;
|
||||
|
||||
return $this->output ?: \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->multipleRedirect( $this->url, ( ( isset( \IPS\Request::i()->mr_continue ) and isset( \IPS\Request::i()->mr ) ) ? \IPS\Request::i()->mr : 0 ), isset( \IPS\Request::i()->_wizardHeight ) ? \intval( \IPS\Request::i()->_wizardHeight ) : NULL );
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ class _Content extends Table
|
||||
* @param NULL|callable $callback Method to call to prepare the returned rows
|
||||
* @param bool $getFirstComment Get the first comment for this item
|
||||
* @param bool $getFollowerCount Get the follower counts for this item
|
||||
* @param bool $getReactions Get reactions for this item
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $class, \IPS\Http\Url $baseUrl, $where=NULL, \IPS\Node\Model $container=NULL, $includeHidden=\IPS\Content\Hideable::FILTER_AUTOMATIC, $permCheck='view', $honorPinned=TRUE, $showMovedLinks=FALSE, $callback=NULL, $getFirstComment=FALSE, $getFollowerCount=FALSE, $getReactions=FALSE )
|
||||
@@ -342,7 +343,8 @@ class _Content extends Table
|
||||
$this->pages = ceil( $this->count / $this->limit );
|
||||
$rows = iterator_to_array( $it );
|
||||
$idField = $class::$databaseColumnId;
|
||||
|
||||
$nodeClass = isset( $class::$containerNodeClass ) ? $class::$containerNodeClass : NULL;
|
||||
|
||||
if ( isset( $this->getFirstComment ) and isset( $class::$databaseColumnMap['first_comment_id'] ) and $class::$firstCommentRequired )
|
||||
{
|
||||
$commentIds = array();
|
||||
@@ -433,26 +435,81 @@ class _Content extends Table
|
||||
|
||||
/* Pre load the contentPostedIn data to save separate DISTINCT queries later and a query for each unread item */
|
||||
if ( isset( $class::$databaseColumnMap['container'] ) )
|
||||
{
|
||||
{
|
||||
$containerItemIds = array();
|
||||
foreach( $rows as $item )
|
||||
foreach ( $rows as $item )
|
||||
{
|
||||
$colContainer = $class::$databaseColumnMap['container'];
|
||||
$colPrimary = $class::$databaseColumnId;
|
||||
|
||||
$containerItemIds[ $item->$colContainer ][] = $item->$colPrimary;
|
||||
$colPrimary = $class::$databaseColumnId;
|
||||
|
||||
$containerItemIds[$item->$colContainer][] = $item->$colPrimary;
|
||||
}
|
||||
|
||||
$nodeClass = $class::$containerNodeClass;
|
||||
foreach( $containerItemIds as $container => $ids )
|
||||
$memberIds = array();
|
||||
$memberPostedIn = array();
|
||||
|
||||
foreach ( $containerItemIds as $container => $ids )
|
||||
{
|
||||
try
|
||||
{
|
||||
$node = $nodeClass::load( $container );
|
||||
$this->contentPostedIn = array_merge( $node->contentPostedIn( NULL, $ids ), $this->contentPostedIn );
|
||||
|
||||
|
||||
if ( \IPS\IPS::classUsesTrait( $node, 'IPS\Node\Statistics' ) )
|
||||
{
|
||||
$this->contentPostedIn = array_merge( $node->contentPostedIn( NULL, $ids ), $this->contentPostedIn );
|
||||
|
||||
foreach ( $node->authorsPostedIn( $ids ) as $memberId => $itemIds )
|
||||
{
|
||||
if ( $memberId > 0 )
|
||||
{
|
||||
$memberIds[$memberId] = $memberId;
|
||||
|
||||
foreach ( $itemIds as $id )
|
||||
{
|
||||
$memberPostedIn[$id][] = $memberId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \OutOfRangeException $ex ) { }
|
||||
}
|
||||
|
||||
if ( \count( $memberIds ) )
|
||||
{
|
||||
/* Get the groups */
|
||||
$groups = array();
|
||||
foreach( \IPS\Db::i()->select( 'member_id, member_group_id, mgroup_others', 'core_members', array( \IPS\Db::i()->in( 'member_id', $memberIds ) ) ) as $row )
|
||||
{
|
||||
$groups[ $row['member_id'] ] = array( 'primary' => $row['member_group_id'], 'secondary' => $row['mgroup_others'] );
|
||||
}
|
||||
|
||||
$thisRow = array();
|
||||
foreach( $memberPostedIn as $itemId => $members )
|
||||
{
|
||||
foreach( $members as $m )
|
||||
{
|
||||
if ( isset( $groups[ $m ]['primary'] ) )
|
||||
{
|
||||
$thisRow[ $itemId ]['primary'][ $groups[ $m ]['primary'] ] = $groups[ $m ]['primary'];
|
||||
}
|
||||
|
||||
if ( isset( $groups[ $m ]['secondary'] ) )
|
||||
{
|
||||
foreach( explode( ',', $groups[ $m ]['secondary'] ) as $secondary )
|
||||
{
|
||||
if ( $secondary )
|
||||
{
|
||||
$thisRow[$itemId]['secondary'][$secondary] = $secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach( $rows as $id => $item )
|
||||
{
|
||||
$rows[$id]->groupsPosted = isset( $thisRow[$item->$idField] ) ? \IPS\Member\Group::postedIn( ( isset( $thisRow[$item->$idField]['primary'] ) ? $thisRow[$item->$idField]['primary'] : array() ), ( isset( $thisRow[$item->$idField]['secondary'] ) ? $thisRow[$item->$idField]['secondary'] : array() ) ) : FALSE;
|
||||
}
|
||||
catch( \OutOfRangeException $ex ) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,7 +733,7 @@ class _Content extends Table
|
||||
if( $item->$action() )
|
||||
{
|
||||
$options[ $id ] = $item->mapped('title');
|
||||
$descriptions[ $id ] = \IPS\Member::loggedIn()->language()->addToStack( 'byline', FALSE, array( 'sprintf' => array( $item->author()->name ) ) ) . \IPS\DateTime::ts( $item->mapped('date') )->html();
|
||||
$descriptions[ $id ] = \IPS\Member::loggedIn()->language()->addToStack( 'byline_merge', FALSE, array( 'htmlsprintf' => array( $item->author()->name, \IPS\DateTime::ts( $item->mapped('date') )->html() ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -807,6 +864,8 @@ class _Content extends Table
|
||||
/* Everything else: just do it */
|
||||
else
|
||||
{
|
||||
$containers = array();
|
||||
|
||||
foreach ( array_keys( \IPS\Request::i()->moderate ) as $id )
|
||||
{
|
||||
try
|
||||
@@ -819,8 +878,24 @@ class _Content extends Table
|
||||
$unread = $object->unread();
|
||||
}
|
||||
|
||||
/* Turn off updating of containers for each action and get container data */
|
||||
try
|
||||
{
|
||||
$object->skipContainerRebuild = TRUE;
|
||||
$containers[ $object->container()->_id ] = $object->container();
|
||||
}
|
||||
catch( \BadMethodCallException $e ){}
|
||||
|
||||
$object->modAction( \IPS\Request::i()->modaction, \IPS\Member::loggedIn(), $params );
|
||||
|
||||
/* Turn back on updating of container data for each action. Also get container again in the event item was moved and it's new */
|
||||
try
|
||||
{
|
||||
$object->skipContainerRebuild = FALSE;
|
||||
$containers[ $object->container()->_id ] = $object->container();
|
||||
}
|
||||
catch( \BadMethodCallException $e ){}
|
||||
|
||||
/* Mark it as read */
|
||||
if( $object instanceof \IPS\Content\ReadMarkers and \IPS\Request::i()->modaction == 'move' AND $unread == 0 )
|
||||
{
|
||||
@@ -829,6 +904,23 @@ class _Content extends Table
|
||||
}
|
||||
catch ( \Exception $e ) {}
|
||||
}
|
||||
|
||||
/* Now update the containers in one go */
|
||||
foreach( $containers as $container )
|
||||
{
|
||||
if ( $container->_comments !== NULL )
|
||||
{
|
||||
$container->setLastComment();
|
||||
}
|
||||
|
||||
if ( $container->_reviews !== NULL )
|
||||
{
|
||||
$container->setLastReview();
|
||||
}
|
||||
|
||||
$container->resetCommentCounts();
|
||||
$container->save();
|
||||
}
|
||||
}
|
||||
|
||||
\IPS\Output::i()->redirect( $this->baseUrl );
|
||||
|
||||
@@ -148,7 +148,7 @@ class _Db extends Table
|
||||
$type = $type[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch ( $type )
|
||||
{
|
||||
case SEARCH_CUSTOM:
|
||||
@@ -373,7 +373,10 @@ class _Db extends Table
|
||||
$orderBy = "{$this->primarySortBy} {$this->primarySortDirection}, {$orderBy}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Are we downloading? Bypass Table Limit */
|
||||
$limit = \IPS\Request::i()->download ? $count : $this->limit;
|
||||
|
||||
/* Run query */
|
||||
$rows = array();
|
||||
$select = $this->db->select(
|
||||
@@ -381,7 +384,7 @@ class _Db extends Table
|
||||
$this->table,
|
||||
$where,
|
||||
$orderBy,
|
||||
array( ( $this->limit * ( $this->page - 1 ) ), $this->limit )
|
||||
array( ( $this->limit * ( $this->page - 1 ) ), $limit )
|
||||
);
|
||||
|
||||
if ( $this->index )
|
||||
|
||||
@@ -120,7 +120,7 @@ class _Wizard
|
||||
public function __toString()
|
||||
{
|
||||
/* Don't cache for a short while to ensure sessions work */
|
||||
\IPS\Request::i()->setCookie( 'noCache', 1 );
|
||||
\IPS\Output::i()->pageCaching = FALSE;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user