Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

+375 -109
View File
@@ -11,7 +11,7 @@
namespace IPS;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -24,6 +24,17 @@ class _Lang extends \IPS\Node\Model
{
/* !Lang - Static */
/**
* @brief [ActiveRecord] Caches
* @note Defined cache keys will be cleared automatically as needed
*/
protected $caches = array( 'languages', 'listFormats', 'updatecount_languages' );
/**
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array('lang_id', 'lang_marketplace_id');
/**
* @brief Have fetched all?
*/
@@ -81,6 +92,21 @@ class _Lang extends \IPS\Node\Model
return $result;
}
/**
* Get data store
*
* @return array
*/
public static function getStore()
{
if ( !isset( \IPS\Data\Store::i()->languages ) )
{
\IPS\Data\Store::i()->languages = iterator_to_array( \IPS\Db::i()->select( '*', 'core_sys_lang', NULL, 'lang_order' )->setKeyField('lang_id') );
}
return \IPS\Data\Store::i()->languages;
}
/**
* Languages
*
@@ -93,15 +119,7 @@ class _Lang extends \IPS\Node\Model
{
if( $iterator === NULL )
{
if ( isset( \IPS\Data\Store::i()->languages ) )
{
$rows = \IPS\Data\Store::i()->languages;
}
else
{
$rows = iterator_to_array( \IPS\Db::i()->select( '*', 'core_sys_lang', NULL, 'lang_order' )->setKeyField('lang_id') );
\IPS\Data\Store::i()->languages = $rows;
}
$rows = static::getStore();
}
else
{
@@ -190,7 +208,7 @@ class _Lang extends \IPS\Node\Model
/**
* Auto detect language
*
* @param string $acceptLanguage HTTP Accept-Language header
* @param string $httpAcceptLanguage HTTP Accept-Language header
* @return int|NULL ID Of preferred language or NULL if could not be autodetected
*/
public static function autoDetectLanguage( $httpAcceptLanguage )
@@ -239,20 +257,29 @@ class _Lang extends \IPS\Node\Model
/**
* Save translatable language strings
*
* @param string $app Application key
* @param string $key Word key
* @param string|array $values The values
* @param bool $js Expose to JavaScript?
* @param string|int|\IPS\Application|\IPS\Plugin $appOrPlugin Application key or Plugin ID
* @param string $key Word key
* @param string|array $values The values
* @param bool $js Expose to JavaScript?
* @param string|NULL $default If the administrator added this word, this is the default value.
* @return void
*/
public static function saveCustom( $app, $key, $values, $js=FALSE )
public static function saveCustom( $appOrPlugin, $key, $values, $js=FALSE, $default=NULL )
{
$default = '';
/* Values is a string, so use this value for all languages */
if ( !is_array( $values ) )
$isCustom = FALSE;
if ( $default !== NULL )
{
$default = $values;
$isCustom = TRUE;
}
if ( !\is_array( $values ) )
{
if ( $default === NULL )
{
$default = $values;
}
$values = array();
foreach ( static::languages() as $lang )
@@ -262,7 +289,7 @@ class _Lang extends \IPS\Node\Model
}
else
{
if ( count( $values ) == 0 )
if ( \count( $values ) == 0 )
{
return;
}
@@ -272,7 +299,7 @@ class _Lang extends \IPS\Node\Model
{
$values[ $lang->id ] = '';
}
else if ( $lang->default )
else if ( $lang->default AND $default === NULL )
{
$default = $values[ $lang->id ];
}
@@ -288,15 +315,45 @@ class _Lang extends \IPS\Node\Model
}
else
{
\IPS\Db::i()->replace( 'core_sys_lang_words', array(
if ( $appOrPlugin instanceof \IPS\Application )
{
$appOrPlugin = $appOrPlugin->directory;
}
elseif ( $appOrPlugin instanceof \IPS\Plugin )
{
$appOrPlugin = $appOrPlugin->_id;
}
$insert = array(
'lang_id' => $langId,
'word_app' => $app,
'word_app' => ( \is_string( $appOrPlugin ) ) ? $appOrPlugin : NULL,
'word_plugin' => ( \is_numeric( $appOrPlugin ) ) ? $appOrPlugin : NULL,
'word_key' => $key,
'word_default' => $default,
'word_custom' => $value,
'word_js' => $js,
'word_export' => FALSE,
) );
'word_is_custom'=> ( $isCustom === TRUE )
);
try
{
\IPS\Db::i()->replace( 'core_sys_lang_words', $insert );
}
catch( \IPS\Db\Exception $e )
{
/* The upgrader can try to insert custom language strings before the word_is_custom column is added in 4.5 */
if( $e->getCode() == 1054 )
{
unset( $insert['word_is_custom'] );
\IPS\Db::i()->replace( 'core_sys_lang_words', $insert );
}
else
{
throw $e;
}
}
}
if ( isset( static::$multitons[ $langId ] ) )
@@ -313,6 +370,11 @@ class _Lang extends \IPS\Node\Model
{
unset( \IPS\Data\Store::i()->listFormats );
}
if ( \substr( $key, 0, 10 ) === 'num_short_' )
{
unset( \IPS\Data\Store::i()->shortFormats );
}
}
}
@@ -472,7 +534,7 @@ class _Lang extends \IPS\Node\Model
$result = setlocale( LC_ALL, $this->short );
/* Some locales in some PHP versions break things drastically */
if( in_array( 'ips\\db\\_select', get_declared_classes() ) AND !in_array( 'IPS\\Db\\_Select', get_declared_classes() ) )
if( \in_array( 'ips\\db\\_select', get_declared_classes() ) AND !\in_array( 'IPS\\Db\\_Select', get_declared_classes() ) )
{
setlocale( LC_CTYPE, 'en_US.UTF-8' );
}
@@ -502,9 +564,9 @@ class _Lang extends \IPS\Node\Model
if( mb_strpos( $locale, '=' ) !== FALSE )
{
$parts = explode( "=", $locale );
if( in_array( $parts[0], array( 'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME' ) ) )
if( \in_array( $parts[0], array( 'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME' ) ) )
{
setlocale( constant( $parts[0] ), $parts[1] );
setlocale( \constant( $parts[0] ), $parts[1] );
}
}
else
@@ -553,7 +615,7 @@ class _Lang extends \IPS\Node\Model
}
/* And only if iconv() exists */
if( !function_exists( 'iconv' ) )
if( !\function_exists( 'iconv' ) )
{
return $text;
}
@@ -564,8 +626,22 @@ class _Lang extends \IPS\Node\Model
return $text;
}
/* Convert the codepage to UTF-8 and return */
return iconv( "CP" . $this->codepage, "UTF-8", $text );
/* Convert the codepage to UTF-8 (if it is valid) and return */
try
{
if( ( $converted = iconv( "CP" . $this->codepage, "UTF-8", $text ) ) !== FALSE )
{
return $converted;
}
else
{
throw new \ErrorException;
}
}
catch( \ErrorException $e )
{
return $text;
}
}
/**
@@ -627,11 +703,11 @@ class _Lang extends \IPS\Node\Model
$return = array();
$keysToLoad = array();
if ( is_array( $key ) )
if ( \is_array( $key ) )
{
foreach( $key as $k )
{
if ( in_array( $k, array_keys( $this->words ) ) )
if ( \in_array( $k, array_keys( $this->words ), true ) )
{
$return[ $k ] = $this->words[ $k ];
}
@@ -641,7 +717,7 @@ class _Lang extends \IPS\Node\Model
}
}
if ( ! count( $keysToLoad ) )
if ( !\count( $keysToLoad ) )
{
return $return;
}
@@ -665,11 +741,11 @@ class _Lang extends \IPS\Node\Model
}
/* If we're using an array, fill any missings strings with NULL to prevent duplicate queries */
if ( is_array( $key ) )
if ( \is_array( $key ) )
{
foreach( $key as $k )
{
if ( ! in_array( $k, $return ) and ! array_key_exists( $k, $this->words ) )
if ( !\in_array( $k, $return ) and ! array_key_exists( $k, $this->words ) )
{
$return[ $k ] = NULL;
$this->words[ $k ] = NULL;
@@ -677,12 +753,12 @@ class _Lang extends \IPS\Node\Model
}
}
if ( ! count( $return ) )
if ( !\count( $return ) )
{
throw new \UnderflowException( ( is_string( $key ) ? 'lang_not_exists__' . $key : 'lang_not_exists__' . implode( ',', $key ) ) );
throw new \UnderflowException( ( \is_string( $key ) ? 'lang_not_exists__' . $key : 'lang_not_exists__' . implode( ',', $key ) ) );
}
return is_string( $key ) ? $this->words[ $key ] : $return;
return \is_string( $key ) ? $this->words[ $key ] : $return;
}
/**
@@ -711,7 +787,7 @@ class _Lang extends \IPS\Node\Model
$this->outputStack[ $id ]['key'] = $key;
$this->outputStack[ $id ]['options'] = $options;
$this->outputStack[ $id ]['vle'] = $vle;
/* Return */
return $id;
}
@@ -721,17 +797,20 @@ class _Lang extends \IPS\Node\Model
*
* @param string $string Language string to pluralize
* @param array $params Parameters to pluraizlie with
* @param string $formatter Number formatter (default is formatNumber)
* @return string
* @note You can use the following wildcards to do special things
* @li ? is a fallback, so anything not matched will use it
* @li * is a beginning wildcard, so anything that ENDS with the number supplied will match
* @li % is an ending wildcard, so anything that BEGINS with the number supplied will match
* @li # (optional) will be replaced with the actual value
* @example {# [1:test][*2:tests][%3:no tests][?:finals]} will result in 1 test, 2 tests, 12 tests, 3 no tests, 35 no tests, 8 finals
* @example {!1#[1:January][2:February][3:March][4:April][5:May][6:June][7:July][8:August][9:September][10:October][11:November][12:December]} {0#}
* @example {!#[?:%s liked]} %s
* @code
{# [1:test][*2:tests][%3:no tests][?:finals]} will result in 1 test, 2 tests, 12 tests, 3 no tests, 35 no tests, 8 finals
{!1#[1:January][2:February][3:March][4:April][5:May][6:June][7:July][8:August][9:September][10:October][11:November][12:December]} {0#}
{!#[?:%s liked]} %s
* @endcode
*/
public function pluralize( $string, $params )
public function pluralize( $string, $params, $formatter=NULL )
{
$i = 0;
$openCurly = '--' . mt_rand() . '--';
@@ -746,7 +825,7 @@ class _Lang extends \IPS\Node\Model
}, $string );
$numberFormatter = array( $this, 'formatNumber' );
return preg_replace_callback( '/\{!?(\d+?)?#(.*?)\}/', function( $format ) use ( $params, $i, $numberFormatter, $openCurly, $closeCurly )
return preg_replace_callback( '/\{!?(\d+?)?#(.*?)\}/', function( $format ) use ( $params, $i, $numberFormatter, $openCurly, $closeCurly, $formatter )
{
$originalNumber = $format[1];
if ( !$format[1] or $format[1] == '!' )
@@ -756,11 +835,12 @@ class _Lang extends \IPS\Node\Model
}
/* Format now so that 0 is really 0 and not '' or null */
$params[ $format[1] ] = call_user_func( $numberFormatter, $params[ $format[1] ] );
$preFormattedNumber = $params[ $format[1] ]; // We need 7300 later, not 7,3000
$params[ $format[1] ] = $numberFormatter( $params[ $format[1] ] );
$fallback = NULL;
$value = NULL;
$token = '--' . mt_rand() . '--';
/* We want to ensure that manually escaped # are not switched */
$format[2] = str_replace( '\#', $token, $format[2] );
@@ -791,9 +871,10 @@ class _Lang extends \IPS\Node\Model
}
}
$finalFormattedNumber = ( $formatter == 'short' ) ? $this->formatNumberShort( $preFormattedNumber ) : $params[ $format[1] ];
$return = rtrim( ltrim( $format[0], '{' ), '}' );
$return = str_replace( "!{$originalNumber}#", '', $return );
$return = str_replace( array( "{$format[1]}#", '#' ), $params[ $format[1] ], $return );
$return = str_replace( array( "{$format[1]}#", '#' ), $finalFormattedNumber, $return );
$return = preg_replace( '/\[.+\]/', ( $value === NULL ? $fallback : $value ), $return );
$return = str_replace( $token, '#', $return );
@@ -811,9 +892,84 @@ class _Lang extends \IPS\Node\Model
*/
public function formatNumber( $number, $decimals=0 )
{
return number_format( floatval( $number ), floatval( $decimals ), $this->locale['decimal_point'], $this->locale['thousands_sep'] );
return number_format( \floatval( $number ), \floatval( $decimals ), $this->locale['decimal_point'], $this->locale['thousands_sep'] );
}
/**
* Format a number in a short format (e.g. 1.6k, instead of 1,558)
*
* @param number $number The number to format
* @param int $postDecimal Number of numbers to show after the decimal (1.1k, 1.12k, etc)
* @return string
*/
public function formatNumberShort( $number, $postDecimal=1 )
{
$lang = NULL;
$origNumber = $number;
if ( \IPS\IN_DEV )
{
foreach( array( 'num_short_billion', 'num_short_million', 'num_short_thousand' ) as $word )
{
$format[ $word ] = $this->words[ $word ];
}
}
else
{
if ( !isset( \IPS\Data\Store::i()->shortFormats ) )
{
$formats = array();
foreach ( \IPS\Db::i()->select( array( 'lang_id', 'word_key', 'word_custom', 'word_default' ), 'core_sys_lang_words', array( \IPS\Db::i()->in('word_key', array( 'num_short_billion', 'num_short_million', 'num_short_thousand' ) ) ) ) as $row )
{
$formats[ $row['lang_id'] ][ $row['word_key'] ] = $row['word_custom'] ?: $row['word_default'];
}
\IPS\Data\Store::i()->shortFormats = $formats;
}
$format = \IPS\Data\Store::i()->shortFormats[ $this->id ];
}
/* Hundreds */
if ( $number >= 1000 )
{
$number = round( $number, -3 + $postDecimal );
}
/* 1 billion or more */
if ( $number >= 1000000000 )
{
$number = number_format( $number / 1000000000, $postDecimal, $this->locale['decimal_point'], $this->locale['thousands_sep'] );
$lang = 'num_short_billion';
}
/* all the millions */
else if ( $number >= 1000000 )
{
$number = number_format( $number / 1000000, $postDecimal, $this->locale['decimal_point'], $this->locale['thousands_sep'] );
$lang = 'num_short_million';
}
/* 1,000 to 999,000 */
else if ( $number >= 1000 )
{
$number = number_format( $number / 1000, $postDecimal, $this->locale['decimal_point'], $this->locale['thousands_sep'] );
$lang = 'num_short_thousand';
}
// Ensure we don't return 1.0, but rather 1
if ( \substr( $number, -2 ) === $this->locale['decimal_point'] . "0" )
{
$number = \substr($number, 0, -2);
}
if ( $lang )
{
return sprintf( $format[ $lang ], $number );
}
return $origNumber;
}
/**
* Format List
* Takes an array and returns a string, appropriate for the language (e.g. "a, b and c")
@@ -859,7 +1015,7 @@ class _Lang extends \IPS\Node\Model
preg_match( '/(^|^(.+?)\s)\b(a)\b(.+?\s?)\b(b)\b(.+?\s?)\b(c)\b(.+?)?$/i', $format, $matches );
$return = $matches[1];
for ( $i = 0; $i<count( $items ); $i++ )
for ( $i = 0; $i<\count( $items ); $i++ )
{
/* Pluralize can be used after, so avoid triggering the '#', '#[...]' syntax there */
$items[ $i ] = preg_replace( '/#(\s|\[)/', '\#\1', $items[ $i ] );
@@ -868,7 +1024,7 @@ class _Lang extends \IPS\Node\Model
{
$upper = ( $matches[3] === 'A' );
}
elseif ( $i == count( $items ) - 1 )
elseif ( $i == \count( $items ) - 1 )
{
$upper = ( $matches[7] === 'C' );
}
@@ -879,11 +1035,11 @@ class _Lang extends \IPS\Node\Model
$return .= ( $upper ? ucfirst( $items[ $i ] ) : $items[ $i ] );
if ( $i == count( $items ) - 2 )
if ( $i == \count( $items ) - 2 )
{
$return .= $matches[6];
}
elseif ( $i != count( $items ) - 1 )
elseif ( $i != \count( $items ) - 1 )
{
$return .= $matches[4];
}
@@ -1036,7 +1192,8 @@ class _Lang extends \IPS\Node\Model
/**
* Add locale field to form
*
* @param \IPS\Helpers\Form $form The form
* @param \IPS\Helpers\Form $form The form
* @param string $current The current locale
* @return void
*/
public static function localeField( &$form, $current='en_US' )
@@ -1068,7 +1225,7 @@ class _Lang extends \IPS\Node\Model
$form->hiddenValues['lang_short'] = 'x';
}
$form->add( new \IPS\Helpers\Form\Text( 'lang_short_custom', !in_array( $current, $commonLocales ) ? $current : NULL, FALSE, array( 'placeholder' => 'en_US' ), '\IPS\Lang::validateLocale', NULL, NULL, 'locale_custom' ) );
$form->add( new \IPS\Helpers\Form\Text( 'lang_short_custom', !\in_array( $current, $commonLocales ) ? $current : NULL, FALSE, array( 'placeholder' => 'en_US' ), '\IPS\Lang::validateLocale', NULL, NULL, 'locale_custom' ) );
}
/**
@@ -1090,9 +1247,10 @@ class _Lang extends \IPS\Node\Model
if( isset( $values['lang_short'] ) )
{
$currentLocale = setlocale( LC_ALL, '0' );
$currentLocale = setlocale( LC_ALL, '0' );
$localesToTest = ( mb_strtolower( mb_substr( PHP_OS, 0, 3 ) ) !== 'win' ) ? array( "{$values['lang_short']}.UTF-8", "{$values['lang_short']}.UTF8" ) : array( $values['lang_short'] );
foreach ( array( "{$values['lang_short']}.UTF-8", "{$values['lang_short']}.UTF8" ) as $l )
foreach ( $localesToTest as $l )
{
$test = setlocale( LC_ALL, $l );
if ( $test !== FALSE )
@@ -1115,7 +1273,6 @@ class _Lang extends \IPS\Node\Model
{
$this->enabled = TRUE;
\IPS\Db::i()->update( 'core_sys_lang', array( 'lang_default' => 0 ) );
unset( \IPS\Data\Store::i()->languages );
}
return $values;
@@ -1141,6 +1298,54 @@ class _Lang extends \IPS\Node\Model
{
return "ipsFlag ipsFlag-{$this->getCountry()}";
}
/**
* [Node] Return the custom badge for each row
*
* @return NULL|array Null for no badge, or an array of badge data (0 => CSS class type, 1 => language string, 2 => optional raw HTML to show instead of language string)
*/
protected function get__badge()
{
/* Is there an update to show? */
$badge = NULL;
if ( $this->update_data )
{
$data = json_decode( $this->update_data, TRUE );
if( !empty( $data['longversion'] ) AND $data['longversion'] > $this->version_long )
{
$released = NULL;
if( $data['released'] AND \intval( $data['released'] ) == $data['released'] AND \strlen( $data['released'] ) == 10 )
{
$released = (string) \IPS\DateTime::ts( $data['released'] )->localeDate();
}
else if( $data['released'] )
{
$released = $data['released'];
}
$badge = array(
0 => 'new',
1 => '',
2 => \IPS\Theme::i()->getTemplate( 'global', 'core' )->updatebadge( $data['version'], $data['updateurl'], $released )
);
}
}
return $badge;
}
/**
* [Node] Get Description
*
* @return string|null
*/
protected function get__description()
{
return \IPS\Theme::i()->getTemplate( 'customization', 'core' )->langDescription( $this );
}
/**
* Return country code
@@ -1234,7 +1439,7 @@ class _Lang extends \IPS\Node\Model
$buttons = array_merge( $buttons, parent::getButtons( $url, $subnode ) );
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'languages', 'lang_words' ) )
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'languages', 'lang_words' ) AND !$this->marketplace_id )
{
$buttons['upload'] = array(
'icon' => 'upload',
@@ -1268,7 +1473,7 @@ class _Lang extends \IPS\Node\Model
$buttons['devimport'] = array(
'icon' => 'cogs',
'title' => 'lang_dev_import',
'link' => \IPS\Http\Url::internal( "app=core&module=languages&controller=languages&do=devimport&id={$this->_id}" ),
'link' => \IPS\Http\Url::internal( "app=core&module=languages&controller=languages&do=devimport&id={$this->_id}" )->csrf(),
);
}
@@ -1391,24 +1596,43 @@ class _Lang extends \IPS\Node\Model
public function set_short( $val )
{
$oldLocale = setlocale( LC_ALL, '0' );
setlocale( LC_ALL, $val );
$result = setlocale( LC_ALL, $val );
/* Some locales in some PHP versions break things drastically */
if( in_array( 'ips\\db\\_select', get_declared_classes() ) )
if( \in_array( 'ips\\db\\_select', \get_declared_classes() ) )
{
setlocale( LC_CTYPE, 'en_US.UTF-8' );
}
/* If this is Windows, store the codepage as we will need it again later */
if( mb_strtoupper( mb_substr( PHP_OS, 0, 3 ) ) === 'WIN' )
{
$codepage = preg_replace( "/^(.+?)\.(.+?)$/i", "$2", $result );
if( $codepage !== $result )
{
$this->codepage = $codepage;
}
}
$this->locale = localeconv();
foreach( $this->locale as $k => $v )
{
if( \is_string( $v ) )
{
$this->locale[ $k ] = $this->convertString( $v );
}
}
foreach( explode( ";", $oldLocale ) as $locale )
{
if( mb_strpos( $locale, '=' ) !== FALSE )
{
$parts = explode( "=", $locale );
if( in_array( $parts[0], array( 'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME' ) ) )
if( \in_array( $parts[0], array( 'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME' ) ) )
{
setlocale( constant( $parts[0] ), $parts[1] );
setlocale( \constant( $parts[0] ), $parts[1] );
}
}
else
@@ -1417,18 +1641,7 @@ class _Lang extends \IPS\Node\Model
}
}
}
/**
* Save Changed Columns
*
* @return void
*/
public function save()
{
parent::save();
unset( \IPS\Data\Store::i()->languages );
}
/**
* Delete Record
*
@@ -1438,7 +1651,6 @@ class _Lang extends \IPS\Node\Model
{
parent::delete();
\IPS\Db::i()->delete( 'core_sys_lang_words', array( 'lang_id=?', $this->id ) );
unset( \IPS\Data\Store::i()->languages );
}
/**
@@ -1450,7 +1662,7 @@ class _Lang extends \IPS\Node\Model
public function parseOutputForDisplay( &$output )
{
/* Do we actually have any? */
if( !count( $this->outputStack ) )
if( !\count( $this->outputStack ) )
{
return;
}
@@ -1466,7 +1678,7 @@ class _Lang extends \IPS\Node\Model
}
}
if( !$this->wordsLoaded === TRUE AND count( $keys ) and !\IPS\IN_DEV )
if( !$this->wordsLoaded === TRUE AND \count( $keys ) and !\IPS\IN_DEV )
{
foreach( \IPS\Db::i()->select( 'word_key, word_default, word_custom', 'core_sys_lang_words', array( "lang_id=? AND word_key IN(" . implode( ",", $keys ) . ") and word_js=0", $this->id ) ) as $row )
{
@@ -1537,12 +1749,12 @@ class _Lang extends \IPS\Node\Model
/**
* Strip VLE tags, useful for AJAX responses where you can't edit the string anyways
*
* @param string $output The output string
* @return string
* @param string|array $output The output string
* @return string|array
*/
public function stripVLETags( $output )
{
if( !is_array( $output ) )
if( !\is_array( $output ) )
{
return preg_replace( "/#VLE#.+?#\!#\[(.+?)\]#\!##/", "$1", $output );
}
@@ -1556,20 +1768,6 @@ class _Lang extends \IPS\Node\Model
return $replacement;
}
/**
* Insert <wbr> tags into long words
*
* @deprecated This is left here to prevent breaking third party addons, but no longer breaks words.
* @note You should use a container div with classes ipsContained and ipsType_break instead.
* Often you may want to also use ipsTruncate and ipsTruncate_line on an inline element (i.e. an <a> tag) wrapping the text as well to truncate.
* @param string $data The string
* @return string
*/
public static function wordbreak( $data )
{
return $data;
}
/**
* Replace values in array recursively
@@ -1585,8 +1783,13 @@ class _Lang extends \IPS\Node\Model
/* Reduce the number of str_replace with arrays */
static $replaceTable = array();
if ( ! is_array( $haystack ) )
if ( !\is_array( $haystack ) )
{
if( !\is_string( $haystack ) )
{
return $haystack;
}
$hash = md5( json_encode($find) . json_encode($replace) . $haystack );
if ( isset( $replaceTable[ $hash ] ) )
@@ -1672,6 +1875,14 @@ class _Lang extends \IPS\Node\Model
},
$replacement
);
$replacement = preg_replace_callback(
"/{internal\.(.+?)\.csrf}/",
function ( $matches )
{
return \IPS\Http\Url::internal( $matches[ 1 ] )->csrf();
},
$replacement
);
$replacement = preg_replace_callback(
"/{internal\.(.+?)}/",
function ( $matches )
@@ -1681,7 +1892,16 @@ class _Lang extends \IPS\Node\Model
$replacement
);
}
/* We do pluralize first in case you have something like "{!#[1:%s has][?:%s have]} %s: %s" */
if ( !empty( $values[ 'options' ][ 'pluralize' ] ) )
{
$replacement = $this->pluralize(
$replacement,
$values[ 'options' ][ 'pluralize' ],
isset( $values[ 'options' ]['format'] ) ? $values[ 'options' ]['format'] : NULL
);
}
$sprintf = array();
@@ -1707,7 +1927,7 @@ class _Lang extends \IPS\Node\Model
{
return htmlspecialchars( trim( $val ), ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', false );
},
( is_array(
( \is_array(
$values[ 'options' ][ 'sprintf' ]
) ? $values[ 'options' ][ 'sprintf' ] : explode( ',', $values[ 'options' ][ 'sprintf' ] ) )
);
@@ -1717,7 +1937,7 @@ class _Lang extends \IPS\Node\Model
{
$sprintf = array_merge(
$sprintf,
( is_array(
( \is_array(
$values[ 'options' ][ 'htmlsprintf' ]
) ? $values[ 'options' ][ 'htmlsprintf' ] : explode(
',',
@@ -1726,7 +1946,7 @@ class _Lang extends \IPS\Node\Model
);
}
if ( count( $sprintf ) )
if ( \count( $sprintf ) )
{
try
{
@@ -1743,17 +1963,10 @@ class _Lang extends \IPS\Node\Model
// If there's the wrong number of parameters because the translator's done it wrong, we can just use empty strings for replacements
}
}
if ( !empty( $values[ 'options' ][ 'pluralize' ] ) )
{
$replacement = $this->pluralize(
$replacement,
$values[ 'options' ][ 'pluralize' ]
);
}
}
else
{
$values['options']['escape'] = TRUE; // Prevent accidentally introducing an XSS vulnerability by using raw strings where something expects a language string
$replacement = $values[ 'key' ];
}
}
@@ -1800,6 +2013,16 @@ class _Lang extends \IPS\Node\Model
$replacement = strip_tags( $replacement );
}
if ( isset( $values[ 'options' ][ 'urlencode' ] ) )
{
$replacement = urlencode( $replacement );
}
if ( isset( $values[ 'options' ][ 'rawurlencode' ] ) )
{
$replacement = rawurlencode( $replacement );
}
if ( isset( $values[ 'options' ][ 'escape' ] ) )
{
$replacement = htmlspecialchars( $replacement, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', false );
@@ -1826,6 +2049,11 @@ class _Lang extends \IPS\Node\Model
$replacement = sprintf( $values[ 'options' ][ 'returnInto' ], $replacement );
}
if ( isset( $values[ 'options' ][ 'removeLazyLoad' ] ) )
{
$replacement = \IPS\Text\Parser::removeLazyLoad( $replacement );
}
$replacements[ $key ] = $replacement;
}
@@ -1837,4 +2065,42 @@ class _Lang extends \IPS\Node\Model
return $output;
}
/**
* Word Form
*
* @param \IPS\Helpers\Form|NULL An existing form object or NULL to create new
* @return void
*/
public static function wordForm( ?\IPS\Helpers\Form &$form = NULL ): void
{
if ( $form === NULL )
{
$form = new \IPS\Helpers\Form;
}
if ( \IPS\Application::appIsEnabled( 'cms' ) )
{
$form->addMessage( 'add_word_reason_pages', 'ipsMessage ipsMessage_info' );
}
else
{
$form->addMessage( 'add_word_reason', 'ipsMessage ipsMessage_info' );
}
$form->add( new \IPS\Helpers\Form\Text( 'word_key', NULL, TRUE, array( 'regex' => '/^([A-Z_][A-Z0-9_]+?)$/i', 'placeholder' => 'my_custom_phrase' ), function( $val ) {
try
{
/* This is a generic check - if key exists in *any* language, don't allow. */
\IPS\Db::i()->select( '*', 'core_sys_lang_words', array( "word_key=?", $val ) )->first();
/* Here? No excepton thrown so error */
throw new \DomainException( 'word_key_exists' );
}
catch( \UnderflowException $e ) {}
} ) );
$form->add( new \IPS\Helpers\Form\TextArea( 'word_default', NULL, TRUE, array( 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack( 'my_custom_phrase' ) ) ) );
if ( \count( static::languages() ) > 1 )
{
$form->add( new \IPS\Helpers\Form\Translatable( 'word_custom', NULL, FALSE, array( 'textArea' => TRUE ) ) );
}
}
}