Version 4.7.8
This commit is contained in:
1 parent
7b7404e741
commit
fd22d99e69
688 files changed
+19814
-10890
No files matched your search
+2
-1
@@ -7,7 +7,7 @@
|
||||
* primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
|
||||
* FILE, changes will be overwritten the next time the script is run.
|
||||
*
|
||||
* @version 4.14.0
|
||||
* @version 4.15.0
|
||||
*
|
||||
* @warning
|
||||
* You must *not* include any other HTML Purifier files before this file,
|
||||
@@ -107,6 +107,7 @@ require 'HTMLPurifier/AttrDef/HTML/Bool.php';
|
||||
require 'HTMLPurifier/AttrDef/HTML/Nmtokens.php';
|
||||
require 'HTMLPurifier/AttrDef/HTML/Class.php';
|
||||
require 'HTMLPurifier/AttrDef/HTML/Color.php';
|
||||
require 'HTMLPurifier/AttrDef/HTML/ContentEditable.php';
|
||||
require 'HTMLPurifier/AttrDef/HTML/FrameTarget.php';
|
||||
require 'HTMLPurifier/AttrDef/HTML/ID.php';
|
||||
require 'HTMLPurifier/AttrDef/HTML/Pixels.php';
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
HTML Purifier 4.14.0 - Standards Compliant HTML Filtering
|
||||
HTML Purifier 4.15.0 - Standards Compliant HTML Filtering
|
||||
Copyright (C) 2006-2008 Edward Z. Yang
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
@@ -58,12 +58,12 @@ class HTMLPurifier
|
||||
* Version of HTML Purifier.
|
||||
* @type string
|
||||
*/
|
||||
public $version = '4.14.0';
|
||||
public $version = '4.15.0';
|
||||
|
||||
/**
|
||||
* Constant with version of HTML Purifier.
|
||||
*/
|
||||
const VERSION = '4.14.0';
|
||||
const VERSION = '4.15.0';
|
||||
|
||||
/**
|
||||
* Global configuration object.
|
||||
|
||||
@@ -101,6 +101,7 @@ require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Bool.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Nmtokens.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Class.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Color.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/ContentEditable.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/FrameTarget.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/ID.php';
|
||||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Pixels.php';
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_AttrDef_HTML_ContentEditable extends HTMLPurifier_AttrDef
|
||||
{
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$allowed = array('false');
|
||||
if ($config->get('HTML.Trusted')) {
|
||||
$allowed = array('', 'true', 'false');
|
||||
}
|
||||
|
||||
$enum = new HTMLPurifier_AttrDef_Enum($allowed);
|
||||
|
||||
return $enum->validate($string, $config, $context);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,11 @@
|
||||
class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
|
||||
/**
|
||||
* @type HTMLPurifier_AttrDef_HTML_ID
|
||||
*/
|
||||
public $idDef;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->idDef = new HTMLPurifier_AttrDef_HTML_ID();
|
||||
|
||||
@@ -24,6 +24,11 @@ class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
|
||||
*/
|
||||
private $uri;
|
||||
|
||||
/**
|
||||
* @type HTMLPurifier_AttrDef_Enum
|
||||
*/
|
||||
public $wmode;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
|
||||
|
||||
@@ -41,6 +41,7 @@ class HTMLPurifier_AttrTypes
|
||||
$this->info['IAlign'] = self::makeEnum('top,middle,bottom,left,right');
|
||||
$this->info['LAlign'] = self::makeEnum('top,bottom,left,right');
|
||||
$this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget();
|
||||
$this->info['ContentEditable'] = new HTMLPurifier_AttrDef_HTML_ContentEditable();
|
||||
|
||||
// unimplemented aliases
|
||||
$this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
|
||||
|
||||
@@ -22,6 +22,8 @@ class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
|
||||
// XXX: This whole business with 'wrap' is all a bit unsatisfactory
|
||||
public $elements = array('li' => true, 'ul' => true, 'ol' => true);
|
||||
|
||||
public $whitespace;
|
||||
|
||||
/**
|
||||
* @param array $children
|
||||
* @param HTMLPurifier_Config $config
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ class HTMLPurifier_Config
|
||||
* HTML Purifier's version
|
||||
* @type string
|
||||
*/
|
||||
public $version = '4.14.0';
|
||||
public $version = '4.15.0';
|
||||
|
||||
/**
|
||||
* Whether or not to automatically finalize
|
||||
|
||||
@@ -176,7 +176,7 @@ class HTMLPurifier_ElementDef
|
||||
|
||||
if (!empty($def->content_model)) {
|
||||
$this->content_model =
|
||||
str_replace("#SUPER", $this->content_model, $def->content_model);
|
||||
str_replace("#SUPER", (string)$this->content_model, $def->content_model);
|
||||
$this->child = false;
|
||||
}
|
||||
if (!empty($def->content_model_type)) {
|
||||
|
||||
+4
-4
@@ -398,8 +398,8 @@ class HTMLPurifier_Encoder
|
||||
// characters to their true byte-wise ASCII/UTF-8 equivalents.
|
||||
$str = strtr($str, self::testEncodingSupportsASCII($encoding));
|
||||
return $str;
|
||||
} elseif ($encoding === 'iso-8859-1') {
|
||||
$str = utf8_encode($str);
|
||||
} elseif ($encoding === 'iso-8859-1' && function_exists('mb_convert_encoding')) {
|
||||
$str = mb_convert_encoding($str, 'UTF-8', 'ISO-8859-1');
|
||||
return $str;
|
||||
}
|
||||
$bug = HTMLPurifier_Encoder::testIconvTruncateBug();
|
||||
@@ -450,8 +450,8 @@ class HTMLPurifier_Encoder
|
||||
// Normal stuff
|
||||
$str = self::iconv('utf-8', $encoding . '//IGNORE', $str);
|
||||
return $str;
|
||||
} elseif ($encoding === 'iso-8859-1') {
|
||||
$str = utf8_decode($str);
|
||||
} elseif ($encoding === 'iso-8859-1' && function_exists('mb_convert_encoding')) {
|
||||
$str = mb_convert_encoding($str, 'ISO-8859-1', 'UTF-8');
|
||||
return $str;
|
||||
}
|
||||
trigger_error('Encoding not supported', E_USER_ERROR);
|
||||
|
||||
@@ -17,6 +17,7 @@ class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule
|
||||
'class' => 'Class',
|
||||
'id' => 'ID',
|
||||
'title' => 'CDATA',
|
||||
'contenteditable' => 'ContentEditable',
|
||||
),
|
||||
'Lang' => array(),
|
||||
'I18N' => array(
|
||||
|
||||
+13
-2
@@ -31,6 +31,16 @@ class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_In
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @type SplObjectStorage
|
||||
*/
|
||||
private $markForDeletion;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->markForDeletion = new SplObjectStorage();
|
||||
}
|
||||
|
||||
public function prepare($config, $context)
|
||||
{
|
||||
$this->attrValidator = new HTMLPurifier_AttrValidator();
|
||||
@@ -64,7 +74,7 @@ class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_In
|
||||
|
||||
if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') {
|
||||
// Mark closing span tag for deletion
|
||||
$current->markForDeletion = true;
|
||||
$this->markForDeletion->attach($current);
|
||||
// Delete open span tag
|
||||
$token = false;
|
||||
}
|
||||
@@ -75,7 +85,8 @@ class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_In
|
||||
*/
|
||||
public function handleEnd(&$token)
|
||||
{
|
||||
if ($token->markForDeletion) {
|
||||
if ($this->markForDeletion->contains($token)) {
|
||||
$this->markForDeletion->detach($token);
|
||||
$token = false;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ class HTMLPurifier_Length
|
||||
if ($this->n === '0' && $this->unit === false) {
|
||||
return true;
|
||||
}
|
||||
if (!ctype_lower($this->unit)) {
|
||||
if ($this->unit === false || !ctype_lower($this->unit)) {
|
||||
$this->unit = strtolower($this->unit);
|
||||
}
|
||||
if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) {
|
||||
|
||||
+7
-2
@@ -48,6 +48,11 @@ class HTMLPurifier_Lexer
|
||||
*/
|
||||
public $tracksLineNumbers = false;
|
||||
|
||||
/**
|
||||
* @type HTMLPurifier_EntityParser
|
||||
*/
|
||||
private $_entity_parser;
|
||||
|
||||
// -- STATIC ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -306,8 +311,8 @@ class HTMLPurifier_Lexer
|
||||
{
|
||||
// normalize newlines to \n
|
||||
if ($config->get('Core.NormalizeNewlines')) {
|
||||
$html = str_replace("\r\n", "\n", $html);
|
||||
$html = str_replace("\r", "\n", $html);
|
||||
$html = str_replace("\r\n", "\n", (string)$html);
|
||||
$html = str_replace("\r", "\n", (string)$html);
|
||||
}
|
||||
|
||||
if ($config->get('HTML.Trusted')) {
|
||||
|
||||
@@ -4410,7 +4410,7 @@ class HTML5TreeConstructer
|
||||
|
||||
foreach ($token['attr'] as $attr) {
|
||||
if (!$el->hasAttribute($attr['name'])) {
|
||||
$el->setAttribute($attr['name'], $attr['value']);
|
||||
$el->setAttribute($attr['name'], (string)$attr['value']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ class HTMLPurifier_PropertyListIterator extends FilterIterator
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function accept()
|
||||
{
|
||||
$key = $this->getInnerIterator()->key();
|
||||
|
||||
@@ -20,6 +20,7 @@ class HTMLPurifier_StringHash extends ArrayObject
|
||||
* @param mixed $index
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($index)
|
||||
{
|
||||
$this->accessed[$index] = true;
|
||||
|
||||
@@ -35,7 +35,7 @@ class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
|
||||
public function filter(&$uri, $config, $context)
|
||||
{
|
||||
foreach ($this->blacklist as $blacklisted_host_fragment) {
|
||||
if (strpos($uri->host, $blacklisted_host_fragment) !== false) {
|
||||
if ($uri->host !== null && strpos($uri->host, $blacklisted_host_fragment) !== false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,11 +100,11 @@ class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
|
||||
$string = $uri->toString();
|
||||
// always available
|
||||
$this->replace['%s'] = $string;
|
||||
$this->replace['%r'] = $context->get('EmbeddedURI', true);
|
||||
$token = $context->get('CurrentToken', true);
|
||||
$this->replace['%n'] = $token ? $token->name : null;
|
||||
$this->replace['%m'] = $context->get('CurrentAttr', true);
|
||||
$this->replace['%p'] = $context->get('CurrentCSSProperty', true);
|
||||
$this->replace['%r'] = $context->get('EmbeddedURI', true) ?: '';
|
||||
$token = $context->get('CurrentToken', true) ?: '';
|
||||
$this->replace['%n'] = $token ? $token->name : '';
|
||||
$this->replace['%m'] = $context->get('CurrentAttr', true) ?: '';
|
||||
$this->replace['%p'] = $context->get('CurrentCSSProperty', true) ?: '';
|
||||
// not always available
|
||||
if ($this->secretKey) {
|
||||
$this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey);
|
||||
|
||||
@@ -32,7 +32,7 @@ abstract class _AppQueries
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function queries($app)
|
||||
public static function queries($app): array
|
||||
{
|
||||
return [
|
||||
'moduleAccess' => new \IPS\Api\GraphQL\Queries\ModuleAccess($app)
|
||||
|
||||
@@ -30,7 +30,7 @@ abstract class _CoverPhotoField
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getDefinition($name)
|
||||
public static function getDefinition($name): array
|
||||
{
|
||||
return [
|
||||
'type' => new ObjectType([
|
||||
|
||||
@@ -30,7 +30,7 @@ abstract class _TagsEditField
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getDefinition($name)
|
||||
public static function getDefinition($name): array
|
||||
{
|
||||
return [
|
||||
'type' => new ObjectType([
|
||||
|
||||
@@ -33,10 +33,12 @@ class _GraphQL
|
||||
*
|
||||
* @param string $query The query to execute
|
||||
* @param array $variables Variables to include in query
|
||||
* @param \IPS\Member|NULL $member Member to check or NULL for currently logged in member.
|
||||
* @return array GraphQL response
|
||||
*/
|
||||
public static function execute( string $query, array $variables = [], array $member = NULL )
|
||||
public static function execute( string $query, array $variables = [], ?\IPS\Member $member = NULL )
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
/* Register our GraphQL library */
|
||||
\IPS\IPS::$PSR0Namespaces['GraphQL'] = \IPS\ROOT_PATH . "/system/3rd_party/graphql-php";
|
||||
|
||||
@@ -49,7 +51,7 @@ class _GraphQL
|
||||
$query,
|
||||
NULL, // $rootValue
|
||||
[
|
||||
'member' => \IPS\Member::loggedIn()
|
||||
'member' => $member
|
||||
], // $context
|
||||
$variables
|
||||
);
|
||||
|
||||
@@ -38,8 +38,9 @@ class _ModuleAccess
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
public function args(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +57,7 @@ class _ModuleAccess
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\core\Stream
|
||||
* @return \IPS\Application
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
|
||||
@@ -47,25 +47,25 @@ class _TypeRegistry
|
||||
}
|
||||
|
||||
/**
|
||||
* @return QueryType
|
||||
* @return \IPS\Api\GraphQL\Types\QueryType
|
||||
*/
|
||||
public static function query()
|
||||
public static function query(): \IPS\Api\GraphQL\Types\QueryType
|
||||
{
|
||||
return self::$query ?: (self::$query = new \IPS\Api\GraphQL\Types\QueryType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MutationType
|
||||
* @return \IPS\Api\GraphQL\Types\MutationType
|
||||
*/
|
||||
public static function mutation()
|
||||
public static function mutation(): \IPS\Api\GraphQL\Types\MutationType
|
||||
{
|
||||
return self::$mutation ?: (self::$mutation = new \IPS\Api\GraphQL\Types\MutationType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ItemStateType
|
||||
* @return \IPS\Api\GraphQL\Types\ItemStateType
|
||||
*/
|
||||
public static function itemState()
|
||||
public static function itemState(): \IPS\Api\GraphQL\Types\ItemStateType
|
||||
{
|
||||
return self::$itemState ?: (self::$itemState = new \IPS\Api\GraphQL\Types\ItemStateType());
|
||||
}
|
||||
@@ -73,47 +73,47 @@ class _TypeRegistry
|
||||
/**
|
||||
* @return ImageType
|
||||
*/
|
||||
public static function image()
|
||||
public static function image(): \IPS\Api\GraphQL\Types\ImageType
|
||||
{
|
||||
return self::$image ?: (self::$image = new \IPS\Api\GraphQL\Types\ImageType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReputationType
|
||||
* @return \IPS\Api\GraphQL\Types\ReputationType
|
||||
*/
|
||||
public static function reputation()
|
||||
public static function reputation(): \IPS\Api\GraphQL\Types\ReputationType
|
||||
{
|
||||
return self::$reputation ?: (self::$reputation = new \IPS\Api\GraphQL\Types\ReputationType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RichTextType
|
||||
* @return \IPS\Api\GraphQL\Types\RichTextType
|
||||
*/
|
||||
public static function richText()
|
||||
public static function richText(): \IPS\Api\GraphQL\Types\RichTextType
|
||||
{
|
||||
return self::$richText ?: (self::$richText = new \IPS\Api\GraphQL\Types\RichTextType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return URLType
|
||||
* @return \IPS\Api\GraphQL\Types\UrlType
|
||||
*/
|
||||
public static function url()
|
||||
public static function url(): \IPS\Api\GraphQL\Types\UrlType
|
||||
{
|
||||
return self::$url ?: (self::$url = new \IPS\Api\GraphQL\Types\UrlType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FollowType
|
||||
* @return \IPS\Api\GraphQL\Types\FollowType
|
||||
*/
|
||||
public static function follow()
|
||||
public static function follow(): \IPS\Api\GraphQL\Types\FollowType
|
||||
{
|
||||
return self::$follow ?: (self::$follow = new \IPS\Api\GraphQL\Types\FollowType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ModuleAccessType
|
||||
* @return \IPS\Api\GraphQL\Types\ModuleAccessType
|
||||
*/
|
||||
public static function moduleAccess()
|
||||
public static function moduleAccess(): \IPS\Api\GraphQL\Types\ModuleAccessType
|
||||
{
|
||||
return self::$moduleAccess ?: (self::$moduleAccess = new \IPS\Api\GraphQL\Types\ModuleAccessType());
|
||||
}
|
||||
@@ -121,7 +121,7 @@ class _TypeRegistry
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\IDType
|
||||
*/
|
||||
public static function id()
|
||||
public static function id(): \GraphQL\Type\Definition\IDType
|
||||
{
|
||||
return Type::id();
|
||||
}
|
||||
@@ -129,7 +129,7 @@ class _TypeRegistry
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\StringType
|
||||
*/
|
||||
public static function string()
|
||||
public static function string(): \GraphQL\Type\Definition\StringType
|
||||
{
|
||||
return Type::string();
|
||||
}
|
||||
@@ -137,7 +137,7 @@ class _TypeRegistry
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\IntType
|
||||
*/
|
||||
public static function int()
|
||||
public static function int(): \GraphQL\Type\Definition\IntType
|
||||
{
|
||||
return Type::int();
|
||||
}
|
||||
@@ -145,7 +145,7 @@ class _TypeRegistry
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\FloatType
|
||||
*/
|
||||
public static function float()
|
||||
public static function float(): \GraphQL\Type\Definition\FloatType
|
||||
{
|
||||
return Type::float();
|
||||
}
|
||||
@@ -153,7 +153,7 @@ class _TypeRegistry
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\BooleanType
|
||||
*/
|
||||
public static function boolean()
|
||||
public static function boolean(): \GraphQL\Type\Definition\BooleanType
|
||||
{
|
||||
return Type::boolean();
|
||||
}
|
||||
@@ -161,7 +161,7 @@ class _TypeRegistry
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\ListOfType
|
||||
*/
|
||||
public static function listOf($type)
|
||||
public static function listOf($type): \GraphQL\Type\Definition\ListOfType
|
||||
{
|
||||
return new ListOfType($type);
|
||||
}
|
||||
@@ -169,12 +169,12 @@ class _TypeRegistry
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\EnumType
|
||||
*/
|
||||
public static function eNum($config)
|
||||
public static function eNum($config): \GraphQL\Type\Definition\EnumType
|
||||
{
|
||||
return new EnumType($config);
|
||||
}
|
||||
|
||||
public static function inputObjectType($config)
|
||||
public static function inputObjectType($config): \GraphQL\Type\Definition\InputObjectType
|
||||
{
|
||||
return new InputObjectType($config);
|
||||
}
|
||||
@@ -183,7 +183,7 @@ class _TypeRegistry
|
||||
* @param Type $type
|
||||
* @return NonNull
|
||||
*/
|
||||
public static function nonNull($type)
|
||||
public static function nonNull($type): \GraphQL\Type\Definition\NonNull
|
||||
{
|
||||
return new NonNull($type);
|
||||
}
|
||||
|
||||
@@ -84,13 +84,10 @@ class _MutationType extends ObjectType
|
||||
}
|
||||
|
||||
return $mutationResolvers;
|
||||
|
||||
$mutationClass = new $className;
|
||||
return $mutationClass->resolve($val, $args, $context, $info);
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_RESOLVE', '3S291/2', 405 );
|
||||
throw new \IPS\Api\Exception( 'NO_RESOLVE', '3S291/2_graphl', 405 );
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -131,10 +131,24 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
'toggles' => array(
|
||||
'invision' => array( 'oauth_grant_types_invision', 'oauth_invision_endpoint' ),
|
||||
'wordpress' => array( 'oauth_wordpress_endpoint' ),
|
||||
'confidential' => array( 'oauth_grant_types_confidential', 'oauth_redirect_uris', 'oauth_choose_scopes', 'oauth_header_oauth_access_tokens', 'oauth_access_token_length', 'oauth_tab_oauth_scopes' ),
|
||||
'public' => array( 'oauth_grant_types_public', 'oauth_redirect_uris', 'oauth_choose_scopes', 'oauth_header_oauth_access_tokens', 'oauth_access_token_length', 'oauth_tab_oauth_scopes' ),
|
||||
'confidential' => array( 'oauth_grant_types_confidential', 'oauth_redirect_uris', 'oauth_choose_scopes', 'oauth_header_oauth_access_tokens', 'oauth_access_token_length', 'oauth_tab_oauth_scopes','oauth_api_access', ),
|
||||
'public' => array( 'oauth_grant_types_public', 'oauth_redirect_uris', 'oauth_choose_scopes', 'oauth_header_oauth_access_tokens', 'oauth_access_token_length', 'oauth_tab_oauth_scopes', 'oauth_api_access' ),
|
||||
)
|
||||
) ) );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'oauth_api_access', $this->api_access ? $this->api_access : 'rest', TRUE, array(
|
||||
'options' => array(
|
||||
'rest' => 'oauth_api_type_rest',
|
||||
'graphql' => 'oauth_api_type_graphql',
|
||||
'both' => 'oauth_api_type_both',
|
||||
),
|
||||
'toggles' => array(
|
||||
'rest' => array( 'oauth_tab_oauth_scopes', 'oauth_choose_scopes' ),
|
||||
'both' => array( 'oauth_tab_oauth_scopes', 'oauth_choose_scopes' ),
|
||||
'graphql' => array( '' ),
|
||||
)
|
||||
), NULL, NULL, NULL, 'oauth_api_access' ) );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'oauth_invision_grant_type', $this->client_id ? $this->grant_types : 'authorization_code', NULL, array(
|
||||
'options' => array(
|
||||
'authorization_code' => 'invision_grant_type_server_authorization_code',
|
||||
@@ -304,6 +318,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
$values['oauth_access_token_length'] = 168;
|
||||
$values['oauth_use_refresh_tokens'] = TRUE;
|
||||
$values['oauth_refresh_token_length'] = 28;
|
||||
$values['oauth_api_access'] = 'rest';
|
||||
$values['scopes'] = array(
|
||||
array(
|
||||
'name' => array( 'key' => 'profile', 'desc' => \IPS\Member::loggedIn()->language()->get('oauth_default_scope_profile') ),
|
||||
@@ -330,6 +345,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
$values['oauth_access_token_length'] = 168;
|
||||
$values['oauth_use_refresh_tokens'] = TRUE;
|
||||
$values['oauth_refresh_token_length'] = 28;
|
||||
$values['oauth_api_access'] = 'rest';
|
||||
$values['scopes'] = array(
|
||||
array(
|
||||
'name' => array( 'key' => 'email', 'desc' => \IPS\Member::loggedIn()->language()->get('oauth_default_scope_email') ),
|
||||
@@ -342,6 +358,11 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
$values['oauth_type'] = 'wordpress';
|
||||
$values['oauth_pkce'] = 'none';
|
||||
}
|
||||
elseif ( $values['oauth_api_access'] === 'graphql' )
|
||||
{
|
||||
$values['oauth_choose_scopes'] = FALSE;
|
||||
$values['scopes'] = array();
|
||||
}
|
||||
else
|
||||
{
|
||||
$values['oauth_type'] = NULL;
|
||||
@@ -412,6 +433,19 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
/* Return */
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
public function get__badge()
|
||||
{
|
||||
return array(
|
||||
0 => 'ipsBadge ipsBadge_neutral ipsPos_right ipsMargin_right:half',
|
||||
1 => 'api_access_' . $this->api_access,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get Description
|
||||
|
||||
@@ -134,9 +134,9 @@ class _Application extends \IPS\Node\Model
|
||||
/**
|
||||
* Get Applications
|
||||
*
|
||||
* @return array
|
||||
* @return array<\IPS\Application>
|
||||
*/
|
||||
public static function applications()
|
||||
public static function applications(): array
|
||||
{
|
||||
if( static::$gotAll === FALSE )
|
||||
{
|
||||
@@ -182,9 +182,9 @@ class _Application extends \IPS\Node\Model
|
||||
/**
|
||||
* Get enabled applications
|
||||
*
|
||||
* @return array
|
||||
* @return array<\IPS\Application>
|
||||
*/
|
||||
public static function enabledApplications()
|
||||
public static function enabledApplications(): array
|
||||
{
|
||||
$applications = static::applications();
|
||||
$enabled = array();
|
||||
@@ -4870,6 +4870,10 @@ EOF;
|
||||
|
||||
/* Delete Anonymous Data */
|
||||
\IPS\Db::i()->delete( 'core_anonymous_posts', \IPS\Db::i()->in( 'anonymous_object_class', $classes ) );
|
||||
|
||||
/* Delete Polls */
|
||||
\IPS\Db::i()->delete( 'core_voters', array( 'poll in (?)', \IPS\Db::i()->select( 'pid', 'core_polls', \IPS\Db::i()->in( 'poll_item_class', $classes ) ) ) );
|
||||
\IPS\Db::i()->delete( 'core_polls', \IPS\Db::i()->in( 'poll_item_class', $classes ) );
|
||||
}
|
||||
|
||||
/* Delete attachment maps - if the attachment is unused, the regular cleanup task will remove the file later */
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
namespace IPS\Content\Api\GraphQL;
|
||||
|
||||
use \IPS\Api\GraphQL\TypeRegistry;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -22,6 +23,18 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
abstract class _ItemMutator extends ContentMutator
|
||||
{
|
||||
public function args(): array
|
||||
{
|
||||
return [
|
||||
'category' => TypeRegistry::nonNull( TypeRegistry::id() ),
|
||||
'title' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'content' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'tags' => TypeRegistry::listOf( TypeRegistry::string() ),
|
||||
'state' => TypeRegistry::itemState(),
|
||||
'postKey' => TypeRegistry::string()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark as read
|
||||
*
|
||||
|
||||
@@ -40,6 +40,13 @@ class _ItemController extends \IPS\Api\Controller
|
||||
{
|
||||
$where[] = array( \IPS\Db::i()->in( $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['container'], array_map( 'intval', array_filter( explode( ',', \IPS\Request::i()->$containerParam ) ) ) ) );
|
||||
}
|
||||
|
||||
/* IDs */
|
||||
if ( isset( \IPS\Request::i()->ids ) )
|
||||
{
|
||||
$idField = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId;
|
||||
$where[] = array(\IPS\Db::i()->in( $idField, array_map( 'intval', explode(',', \IPS\Request::i()->ids ) ) ) );
|
||||
}
|
||||
|
||||
/* Authors */
|
||||
if ( isset( \IPS\Request::i()->authors ) )
|
||||
@@ -488,6 +495,7 @@ class _ItemController extends \IPS\Api\Controller
|
||||
if( !$item->getPoll() )
|
||||
{
|
||||
$poll->starter_id = $item->author()->member_id;
|
||||
$poll->poll_item_class = \get_class( $item );
|
||||
}
|
||||
|
||||
$formatted = array(
|
||||
|
||||
@@ -140,7 +140,7 @@ trait ClubContainer
|
||||
}
|
||||
|
||||
$this->save();
|
||||
\IPS\Db::i()->update( 'core_clubs_node_map', array( 'name' => $values['club_node_name'], 'public' => $values['club_node_public'] ?? 0), array( 'club_id=? AND node_class=? AND node_id=?', $club->id, $nodeClass, $this->_id ) );
|
||||
\IPS\Db::i()->update( 'core_clubs_node_map', array( 'name' => $values['club_node_name'], 'public' => $values['club_node_public'] ? $values['club_node_public'] : 0), array( 'club_id=? AND node_class=? AND node_id=?', $club->id, $nodeClass, $this->_id ) );
|
||||
}
|
||||
|
||||
\IPS\Lang::saveCustom( $itemClass::$application, static::$titleLangPrefix . $this->_id, $values['club_node_name'] );
|
||||
@@ -220,9 +220,10 @@ trait ClubContainer
|
||||
case $club::TYPE_PUBLIC:
|
||||
$insert[ 'perm_' . $v ] = 'ca';
|
||||
break;
|
||||
|
||||
case $club::TYPE_OPEN:
|
||||
case $club::TYPE_CLOSED:
|
||||
$insert[ 'perm_' . $v ] = ( $this->isPublic() == 2 ) ? "*" : "cm,c{$club->id}";
|
||||
break;
|
||||
case $club::TYPE_OPEN:
|
||||
case $club::TYPE_PRIVATE:
|
||||
case $club::TYPE_READONLY:
|
||||
$insert[ 'perm_' . $v ] = "cm,c{$club->id}";
|
||||
@@ -410,17 +411,17 @@ trait ClubContainer
|
||||
/**
|
||||
* Is public
|
||||
*
|
||||
* @return Bool
|
||||
* @return int 0 = not public, 1 = anyone can view, 2 = anyone can participate
|
||||
*/
|
||||
public function isPublic()
|
||||
{
|
||||
try
|
||||
{
|
||||
return (bool) \IPS\Db::i()->select( 'public', 'core_clubs_node_map', array( 'club_id=? AND node_class=? AND node_id=?', $this->club()->id, \get_called_class(), $this->_id ) )->first();
|
||||
return (int) \IPS\Db::i()->select( 'public', 'core_clubs_node_map', array( 'club_id=? AND node_class=? AND node_id=?', $this->club()->id, \get_called_class(), $this->_id ) )->first();
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
return FALSE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,7 +326,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
if( $this->isAnonymous() )
|
||||
{
|
||||
$guest = new \IPS\Member;
|
||||
$guest->name = \IPS\Member::loggedIn()->language()->addToStack( "post_anonymously_placename" );
|
||||
$guest->name = \IPS\Member::loggedIn()->language()->get( "post_anonymously_placename" );
|
||||
return $guest;
|
||||
}
|
||||
elseif ( $this->mapped('author') or !isset( static::$databaseColumnMap['author_name'] ) or !$this->mapped('author_name') )
|
||||
@@ -3158,10 +3158,11 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Get edit history
|
||||
*
|
||||
* @param bool $staff Set true for moderators who have permission to view the full log which will show edits not made by the author and private edits
|
||||
* @param bool $staff Set true for moderators who have permission to view the full log which will show edits not made by the author and private edits
|
||||
* @param int|NULL $limit
|
||||
* @return \IPS\Db\Select
|
||||
*/
|
||||
public function editHistory( $staff=FALSE )
|
||||
public function editHistory( $staff=FALSE, $limit=NULL )
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$where = array( array( 'class=? AND comment_id=?', \get_called_class(), $this->$idColumn ) );
|
||||
@@ -3169,7 +3170,8 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$where[] = array( '`member`=? AND public=1', $this->author()->member_id );
|
||||
}
|
||||
return \IPS\Db::i()->select( '*', 'core_edit_history', $where, 'time DESC' );
|
||||
|
||||
return \IPS\Db::i()->select( '*', 'core_edit_history', $where, 'time DESC', $limit );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -382,6 +382,14 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we're filtering the topic (live topic questions) then the max commentID in the source will not be the max commentID possible
|
||||
as not all comments will be shown */
|
||||
if ( isset( \IPS\Request::i()->ltqid ) )
|
||||
{
|
||||
\IPS\Output::i()->json( array( 'error' => 'auto_polling_disabled' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
/* If guest page caching is on, and we're a guest, disable polling */
|
||||
if ( !\IPS\Member::loggedIn()->member_id and \IPS\CACHE_PAGE_TIMEOUT )
|
||||
{
|
||||
@@ -1271,10 +1279,40 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
\IPS\Output::i()->error( 'node_error', '2S136/1Q', 404, '' );
|
||||
}
|
||||
|
||||
$idColumn = $class::$databaseColumnId;
|
||||
$where = array( array( 'class=? AND comment_id=?', $class, $item->$idColumn ) );
|
||||
if ( !\IPS\Member::loggedIn()->modPermission( 'can_view_editlog' ) )
|
||||
{
|
||||
$where[] = array( '`member`=? AND public=1', $item->author()->member_id );
|
||||
}
|
||||
|
||||
$table = new \IPS\Helpers\Table\Db( 'core_edit_history', $item->url()->setQueryString( array( 'do' => 'editlog' ) ), $where );
|
||||
$table->sortBy = 'time';
|
||||
$table->sortDirection = 'desc';
|
||||
$table->limit = 10;
|
||||
$table->tableTemplate = array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' ), 'commentEditHistoryTable' );
|
||||
$table->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' ), 'commentEditHistoryRows' );
|
||||
$table->parsers = array(
|
||||
'new' => function( $val )
|
||||
{
|
||||
return $val;
|
||||
},
|
||||
'old' => function( $val )
|
||||
{
|
||||
return $val;
|
||||
}
|
||||
);
|
||||
$table->extra = $item;
|
||||
|
||||
$pageParam = $table->getPaginationKey();
|
||||
if( \IPS\Request::i()->isAjax() AND isset( \IPS\Request::i()->$pageParam ) )
|
||||
{
|
||||
\IPS\Output::i()->sendOutput( (string) $table );
|
||||
}
|
||||
|
||||
\IPS\Output::i()->breadcrumb[] = array( NULL, \IPS\Member::loggedIn()->language()->addToStack( 'edit_history_title' ) );
|
||||
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'edit_history_title' );
|
||||
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->commentEditHistory( $item->editHistory( \IPS\Member::loggedIn()->modPermission( 'can_view_editlog' ) ), $item );
|
||||
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->commentEditHistory( (string) $table, $item );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2963,6 +3001,37 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
{
|
||||
throw new \DomainException;
|
||||
}
|
||||
|
||||
$idColumn = $commentClass::$databaseColumnId;
|
||||
$where = array( array( 'class=? AND comment_id=?', $commentClass, $comment->$idColumn ) );
|
||||
if ( !\IPS\Member::loggedIn()->modPermission( 'can_view_editlog' ) )
|
||||
{
|
||||
$where[] = array( '`member`=? AND public=1', $comment->author()->member_id );
|
||||
}
|
||||
|
||||
$table = new \IPS\Helpers\Table\Db( 'core_edit_history', $item->url()->setQueryString( array( 'do' => 'editlogComment', 'comment' => $comment->$idColumn ) ), $where );
|
||||
$table->sortBy = 'time';
|
||||
$table->sortDirection = 'desc';
|
||||
$table->limit = 10;
|
||||
$table->tableTemplate = array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' ), 'commentEditHistoryTable' );
|
||||
$table->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' ), 'commentEditHistoryRows' );
|
||||
$table->parsers = array(
|
||||
'new' => function( $val )
|
||||
{
|
||||
return $val;
|
||||
},
|
||||
'old' => function( $val )
|
||||
{
|
||||
return $val;
|
||||
}
|
||||
);
|
||||
$table->extra = $comment;
|
||||
|
||||
$pageParam = $table->getPaginationKey();
|
||||
if( \IPS\Request::i()->isAjax() AND isset( \IPS\Request::i()->$pageParam ) )
|
||||
{
|
||||
\IPS\Output::i()->sendOutput( (string) $table );
|
||||
}
|
||||
|
||||
/* Display */
|
||||
$container = NULL;
|
||||
@@ -2979,7 +3048,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
\IPS\Output::i()->breadcrumb[] = array( $comment->url(), $item->mapped( 'title' ) );
|
||||
\IPS\Output::i()->breadcrumb[] = array( NULL, \IPS\Member::loggedIn()->language()->addToStack( 'edit_history_title' ) );
|
||||
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'edit_history_title' );
|
||||
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->commentEditHistory( $comment->editHistory( \IPS\Member::loggedIn()->modPermission( 'can_view_editlog' ) ), $comment );
|
||||
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->commentEditHistory( (string) $table, $comment );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3529,6 +3598,12 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
$oldItem->rebuildPopularTime();
|
||||
}
|
||||
|
||||
/* Option to do some post split stuff */
|
||||
if ( method_exists( $item, 'splitComplete' ) )
|
||||
{
|
||||
$item->splitComplete( $oldItem, $item, $comments );
|
||||
}
|
||||
|
||||
/* Now reindex all of the comments - we do this last to prevent the "is last comment" flag continously bouncing around while the comments are still being moved */
|
||||
foreach( $comments as $comment )
|
||||
{
|
||||
|
||||
+95
-26
@@ -320,7 +320,7 @@ abstract class _Item extends \IPS\Content
|
||||
case 'hidden_reviews':
|
||||
$val = 0;
|
||||
break;
|
||||
|
||||
|
||||
case 'is_future_entry':
|
||||
$val = ( $time->getTimestamp() > time() ) ? 1 : 0;
|
||||
break;
|
||||
@@ -425,7 +425,7 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$commentClass = static::$commentClass;
|
||||
|
||||
$comment = $commentClass::create( $obj, $values[ static::$formLangPrefix . 'content' ], TRUE, ( !$member->real_name ) ? NULL : $member->real_name, $obj->hidden() ? FALSE : NULL, $member );
|
||||
$comment = $commentClass::create( $obj, $values[ static::$formLangPrefix . 'content' ], TRUE, ( !$member->real_name ) ? NULL : $member->real_name, $obj->hidden() ? FALSE : NULL, $member, $time );
|
||||
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$commentIdColumn = $commentClass::$databaseColumnId;
|
||||
@@ -560,6 +560,7 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$val = $val ? $val->pid : NULL;
|
||||
}
|
||||
|
||||
foreach ( \is_array( static::$databaseColumnMap[ $k ] ) ? static::$databaseColumnMap[ $k ] : array( static::$databaseColumnMap[ $k ] ) as $column )
|
||||
{
|
||||
$this->$column = $val;
|
||||
@@ -579,6 +580,12 @@ abstract class _Item extends \IPS\Content
|
||||
$this->setTags( $values[ static::$formLangPrefix . 'tags' ] ?: array() );
|
||||
}
|
||||
|
||||
/* Future Publishing */
|
||||
if( static::canFuturePublish( NULL, $this->containerWrapper() ) and isset( static::$databaseColumnMap['future_date'] ) and isset( $values[ static::$formLangPrefix . 'date'] ) )
|
||||
{
|
||||
$this->setFuturePublishingDates( $values );
|
||||
}
|
||||
|
||||
/* Post before registering */
|
||||
if ( isset( $values['guest_email'] ) and ( !$this->containerWrapper() or !$this->containerWrapper()->can( 'add', \IPS\Member::loggedIn(), FALSE ) ) )
|
||||
{
|
||||
@@ -591,6 +598,29 @@ abstract class _Item extends \IPS\Content
|
||||
\IPS\Request::i()->setCookie( 'post_before_register', $this->_logPostBeforeRegistering( $values['guest_email'], isset( \IPS\Request::i()->cookie['post_before_register'] ) ? \IPS\Request::i()->cookie['post_before_register'] : NULL ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set future publishing dates
|
||||
*
|
||||
* @param array $values Values from form
|
||||
* @return void
|
||||
*/
|
||||
protected function setFuturePublishingDates( array $values )
|
||||
{
|
||||
$time = $values[ static::$formLangPrefix . 'date' ] instanceof \IPS\DateTime ? $values[ static::$formLangPrefix . 'date' ] : new \IPS\DateTime;
|
||||
|
||||
if( isset( static::$databaseColumnMap['date'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['date'];
|
||||
$this->$column = ( $time->getTimestamp() > time() ) ? $time->getTimestamp() : time();
|
||||
}
|
||||
|
||||
if( isset( static::$databaseColumnMap['future_date'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['future_date'];
|
||||
$this->$column = $time->getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a given member create this type of content?
|
||||
@@ -688,7 +718,7 @@ abstract class _Item extends \IPS\Content
|
||||
/* Title */
|
||||
if ( isset( static::$databaseColumnMap['title'] ) )
|
||||
{
|
||||
$return['title'] = new \IPS\Helpers\Form\Text( static::$formLangPrefix . 'title', $item ? $item->mapped('title') : ( isset( \IPS\Request::i()->title ) ? \IPS\Request::i()->title : NULL ), TRUE, array( 'maxLength' => \IPS\Settings::i()->max_title_length ?: 255, 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_SWAP ) );
|
||||
$return['title'] = new \IPS\Helpers\Form\Text( static::$formLangPrefix . 'title', isset( \IPS\Request::i()->title ) ? \IPS\Request::i()->title : $item?->mapped( 'title' ), TRUE, array( 'maxLength' => \IPS\Settings::i()->max_title_length ?: 255, 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_SWAP ) );
|
||||
$return['title']->rowClasses[] = 'ipsFieldRow_primary';
|
||||
$return['title']->rowClasses[] = 'ipsFieldRow_fullWidth';
|
||||
}
|
||||
@@ -856,20 +886,69 @@ abstract class _Item extends \IPS\Content
|
||||
/* Create form element */
|
||||
if ( $canCreatePoll )
|
||||
{
|
||||
$return['poll'] = new \IPS\Helpers\Form\Poll( static::$formLangPrefix . 'poll', $existingPoll, FALSE, array( 'allowPollOnly' => TRUE ) );
|
||||
$return['poll'] = new \IPS\Helpers\Form\Poll( static::$formLangPrefix . 'poll', $existingPoll, FALSE, array( 'allowPollOnly' => TRUE, 'itemClass' => \get_called_class() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/* Future date */
|
||||
if ( \in_array( 'IPS\Content\FuturePublishing', class_implements( \get_called_class() ) ) and static::canFuturePublish( NULL, $container ) and isset( static::$databaseColumnMap['future_date'] ) )
|
||||
/* Show the future date field for new items or while editing an item, but only if the item wasn't published yet */
|
||||
if ( static::supportsPublishDate( $item ) and static::canFuturePublish( NULL, $container ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['future_date'];
|
||||
$return['date'] = new \IPS\Helpers\Form\Date( static::$formLangPrefix . 'date', ( $item and $item->$column ) ? \IPS\DateTime::ts( $item->$column ) : 0, FALSE, array( 'time' => TRUE, 'unlimited' => 0, 'unlimitedLang' => 'immediately'), NULL, NULL, NULL, static::$formLangPrefix . 'date' );
|
||||
$return['date'] = static::getPublishDateField( $item );
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Date Field for the publish date
|
||||
*
|
||||
* @param \IPS\Content|NULL $item
|
||||
* @return \IPS\Helpers\Form\Date
|
||||
*/
|
||||
protected static function getPublishDateField( \IPS\Content $item = NULL ): \IPS\Helpers\Form\Date
|
||||
{
|
||||
/* If it's not published, we don't want to allow any past times */
|
||||
$column = static::$databaseColumnMap['future_date'];
|
||||
|
||||
$minFutureTime = static::getMinimumPublishDate();
|
||||
$unlimited = 0;
|
||||
$unlimitedLang = 'immediately';
|
||||
if( $item )
|
||||
{
|
||||
$unlimited = NULL;
|
||||
$unlimitedLang = NULL;
|
||||
}
|
||||
|
||||
return new \IPS\Helpers\Form\Date( static::$formLangPrefix . 'date', ( $item and $item->$column ) ? \IPS\DateTime::ts( $item->$column ) : 0, FALSE, array( 'time' => TRUE, 'unlimited' => $unlimited, 'unlimitedLang' => $unlimitedLang, 'min' => $minFutureTime ), NULL, NULL, NULL, static::$formLangPrefix . 'date' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the publish date be changed while editing the item?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static bool $allowPublishDateWhileEditing = FALSE;
|
||||
|
||||
/**
|
||||
* Whether this content supports future publish dates
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function supportsPublishDate( ?\IPS\Content\Item $item ): bool
|
||||
{
|
||||
return \in_array( 'IPS\Content\FuturePublishing', class_implements( \get_called_class() ) ) and isset( static::$databaseColumnMap['future_date'] ) AND ( ( $item AND ( $item->isFutureDate() OR static::$allowPublishDateWhileEditing ) OR !$item ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the earliest publish date for the new content item , should be the current timestamp for most content types
|
||||
*
|
||||
* @return \IPS\DateTime|null
|
||||
*/
|
||||
protected static function getMinimumPublishDate(): ?\IPS\DateTime
|
||||
{
|
||||
return \IPS\DateTime::create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the tags form element
|
||||
*
|
||||
@@ -1150,6 +1229,13 @@ abstract class _Item extends \IPS\Content
|
||||
$column = $commentClass::$databaseColumnMap['content'];
|
||||
$idField = $commentClass::$databaseColumnId;
|
||||
|
||||
/* Update the comment date, in case the topic scheduled publish date has changed */
|
||||
if( isset( $values[ static::$formLangPrefix . 'date' ] ) and $values[ static::$formLangPrefix . 'date' ] instanceof \IPS\DateTime and isset( $commentClass::$databaseColumnMap['date'] ) )
|
||||
{
|
||||
$dateColumn = $commentClass::$databaseColumnMap['date'];
|
||||
$commentObj->$dateColumn = $values[ static::$formLangPrefix . 'date' ]->getTimestamp();
|
||||
}
|
||||
|
||||
if ( $commentObj instanceof \IPS\Content\EditHistory and \IPS\Settings::i()->edit_log )
|
||||
{
|
||||
$editIsPublic = \IPS\Member::loggedIn()->group['g_append_edit'] ? $values['comment_log_edit'] : TRUE;
|
||||
@@ -1244,16 +1330,6 @@ abstract class _Item extends \IPS\Content
|
||||
$this->publish();
|
||||
}
|
||||
}
|
||||
elseif ( $container AND ! $this->$column )
|
||||
{
|
||||
if ( $values[ static::$formLangPrefix . 'date' ] instanceof \IPS\DateTime AND $values[ static::$formLangPrefix . 'date' ]->getTimestamp() > time() )
|
||||
{
|
||||
/* Was not future, but now is */
|
||||
$dateColumn = static::$databaseColumnMap['future_date'];
|
||||
$this->$dateColumn = $values[ static::$formLangPrefix . 'date' ]->getTimestamp();
|
||||
$this->unPublish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Post anonymously */
|
||||
@@ -8503,7 +8579,7 @@ abstract class _Item extends \IPS\Content
|
||||
|
||||
$this->$future = 1;
|
||||
}
|
||||
|
||||
|
||||
$this->save();
|
||||
$this->onUnpublish( $member );
|
||||
|
||||
@@ -8541,12 +8617,6 @@ abstract class _Item extends \IPS\Content
|
||||
$this->$future = 0;
|
||||
}
|
||||
|
||||
if ( isset( static::$databaseColumnMap['date'] ) )
|
||||
{
|
||||
$dateField = \is_array( static::$databaseColumnMap['date'] ) ? static::$databaseColumnMap['date'][0] : static::$databaseColumnMap['date'];
|
||||
$this->$dateField = time();
|
||||
}
|
||||
|
||||
$this->save();
|
||||
$this->onPublish( $member );
|
||||
|
||||
@@ -8649,7 +8719,6 @@ abstract class _Item extends \IPS\Content
|
||||
*/
|
||||
public function onUnpublish( $member )
|
||||
{
|
||||
$container = NULL;
|
||||
if ( method_exists( $this, 'container' ) )
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1265,16 +1265,20 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/* This forces MySQL to strongly hint a better index to the optimiser for MySQL 5.7+, even though sorting will not be affected */
|
||||
if ( !\IPS\CIC )
|
||||
{
|
||||
if ( $this->filterByUnread !== FALSE and ( ! mb_stristr( \IPS\Db::i()->server_info, '-MariaDB' ) and \IPS\Db::i()->server_version >= 50700 ) )
|
||||
try
|
||||
{
|
||||
$order .= ", index_date_updated";
|
||||
}
|
||||
|
||||
/* If we're filtering by unread, and not using MariaDB, then force an index because MySQL tends to use the incorrect one in this scenario. */
|
||||
if( !mb_stristr( \IPS\Db::i()->server_info, '-MariaDB' ) AND $this->filterByUnread === TRUE )
|
||||
{
|
||||
$this->forceIndex = 'index_date_updated';
|
||||
if ( $this->filterByUnread !== FALSE and ( !mb_stristr( \IPS\Db::i()->server_info, '-MariaDB' ) and \IPS\Db::i()->server_version >= 50700 ) )
|
||||
{
|
||||
$order .= ", index_date_updated";
|
||||
}
|
||||
|
||||
/* If we're filtering by unread, and not using MariaDB, then force an index because MySQL tends to use the incorrect one in this scenario. */
|
||||
if ( !mb_stristr( \IPS\Db::i()->server_info, '-MariaDB' ) and $this->filterByUnread === TRUE )
|
||||
{
|
||||
$this->forceIndex = 'index_date_updated';
|
||||
}
|
||||
}
|
||||
catch( \Throwable $e ) { /* Do nothing if server_info access fails */ }
|
||||
}
|
||||
|
||||
/* But we're sorting by relevancy, we need to actually select that value with our fancy algorithm */
|
||||
|
||||
@@ -51,7 +51,7 @@ class _Pinterest extends \IPS\Content\ShareServices
|
||||
{
|
||||
if ( $this->item )
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'sharelinks', 'core' )->pinterest( \IPS\Http\Url::external( 'http://pinterest.com/pin/create/button/' )->setQueryString( 'url', (string) $this->url )->setQueryString( 'media', (string) $this->item->shareImage() ) );
|
||||
return \IPS\Theme::i()->getTemplate( 'sharelinks', 'core' )->pinterest( \IPS\Http\Url::external( 'https://pinterest.com/pin/create/button/' )->setQueryString( 'url', (string) $this->url )->setQueryString( 'media', (string) $this->item->shareImage() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -222,6 +222,12 @@ trait Solvable
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* If we have no replies, it's not solvable yet */
|
||||
if( $this->commentCount() <= 0 OR ( static::$firstCommentRequired AND $this->commentCount() == 1 ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $this->containerAllowsSolvable() )
|
||||
{
|
||||
|
||||
@@ -35,7 +35,6 @@ class _DateTime extends \DateTime
|
||||
$obj->setTimestamp( (int) $timestamp );
|
||||
if ( !$bypassTimezone AND \IPS\Dispatcher::hasInstance() and \IPS\Member::loggedIn()->timezone )
|
||||
{
|
||||
$validTimezone = TRUE;
|
||||
if( \in_array( \IPS\Member::loggedIn()->timezone, static::getTimezoneIdentifiers() ) )
|
||||
{
|
||||
try
|
||||
@@ -44,24 +43,9 @@ class _DateTime extends \DateTime
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
$validTimezone = FALSE;
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$validTimezone = FALSE;
|
||||
}
|
||||
|
||||
if( ! $validTimezone )
|
||||
{
|
||||
\IPS\Member::loggedIn()->timezone = null;
|
||||
|
||||
if ( \IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
\IPS\Member::loggedIn()->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
@@ -99,7 +83,14 @@ class _DateTime extends \DateTime
|
||||
{
|
||||
return 'America/Argentina/' . \substr( $timezone, 8 );
|
||||
}
|
||||
return $timezone;
|
||||
|
||||
/* Chromium reports the wrong timezones for these locations - https://bugs.chromium.org/p/chromium/issues/detail?id=580195 */
|
||||
return match ( $timezone )
|
||||
{
|
||||
'Asia/Calcutta' => 'Asia/Kolkata',
|
||||
'Asia/Katmandu' => 'Asia/Kathmandu',
|
||||
default => $timezone,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,6 +51,9 @@ class _Developer
|
||||
if( $updated )
|
||||
{
|
||||
\IPS\Plugin\Hook::writeDataFile();
|
||||
|
||||
/* Update JS cache bust */
|
||||
\IPS\Settings::i()->changeValues( array( 'javascript_updated' => time() ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,13 +44,6 @@ class _GraphQL extends Api
|
||||
/* Check our IP address isn't banned */
|
||||
$this->_checkIpAddressIsAllowed();
|
||||
|
||||
/* If this is a pre-flight request, acknowldge it */
|
||||
// @todo review this approach
|
||||
if( $_SERVER['REQUEST_METHOD'] === 'OPTIONS' )
|
||||
{
|
||||
$this->_respond( '', 200 );
|
||||
}
|
||||
|
||||
/* Authenticate */
|
||||
$client = NULL;
|
||||
$this->_setRawCredentials();
|
||||
@@ -60,23 +53,34 @@ class _GraphQL extends Api
|
||||
{
|
||||
$this->_setAccessToken();
|
||||
$client = \IPS\Api\OAuthClient::load( $this->accessToken['client_id'] );
|
||||
|
||||
if( $client->api_access == 'rest' )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_GRAPHQL_ACCESS', '3S426_graphql/1', 403 );
|
||||
}
|
||||
\IPS\Member::$loggedInMember = \IPS\Member::load( $this->accessToken['member_id'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$client = \IPS\Api\OAuthClient::load( $this->rawApiKey );
|
||||
|
||||
/* Check that the API key has access to the GraphQL API */
|
||||
if( $client->api_access == 'rest' )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_GRAPHQL_ACCESS', '3S426_graphql/1', 403 );
|
||||
}
|
||||
\IPS\Member::$loggedInMember = new \IPS\Member;
|
||||
}
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_API_KEY', '3S290/7', 401 );
|
||||
throw new \IPS\Api\Exception( 'INVALID_API_KEY', '3S290_graphql/7', 401 );
|
||||
}
|
||||
|
||||
/* Check that the OAuth client has access to the GraphQL API */
|
||||
if ( !$client->graphql )
|
||||
if( $client->api_access === 'rest' )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_GRAPHQL_ACCESS', '2S291/3', 403 );
|
||||
throw new \IPS\Api\Exception( 'NO_GRAPHQL_ACCESS', '2S291_graphql/3', 403 );
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Api\Exception $e )
|
||||
@@ -85,7 +89,7 @@ class _GraphQL extends Api
|
||||
$response = json_encode( array( 'errors' => array( array( 'message' => $e->getMessage(), 'id' => $e->exceptionCode ) ) ), JSON_PRETTY_PRINT );
|
||||
|
||||
/* Do we need to log this? */
|
||||
if ( \in_array( $e->exceptionCode, array( '2S290/8', '2S290/B', '3S290/7', '3S290/9' ) ) )
|
||||
if ( \in_array( $e->exceptionCode, array( '2S290_graphql/8', '2S290_graphql/B', '3S290_graphql/7', '3S290_graphql/9' ) ) )
|
||||
{
|
||||
$this->_log( $response, $e->getCode(), \in_array( $e->exceptionCode, array( '3S290/7', '3S290/9', '3S290/B' ) ) );
|
||||
}
|
||||
@@ -106,11 +110,22 @@ class _GraphQL extends Api
|
||||
{
|
||||
/* Work out the query (can either use a JSON-encoded or form-encoded) body */
|
||||
$query = NULL;
|
||||
$variables = NULL;
|
||||
$variables = [];
|
||||
if( \IPS\Request::i()->query )
|
||||
{
|
||||
$query = \IPS\Request::i()->query;
|
||||
$variables = ( isset( \IPS\Request::i()->variables ) AND \is_array( \IPS\Request::i()->variables ) ) ? \IPS\Request::i()->variables : [];
|
||||
|
||||
if( isset( \IPS\Request::i()->variables ) )
|
||||
{
|
||||
if( \is_array( \IPS\Request::i()->variables ) )
|
||||
{
|
||||
$variables = \IPS\Request::i()->variables;
|
||||
}
|
||||
else
|
||||
{
|
||||
$variables = json_decode( \IPS\Request::i()->variables, TRUE );
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ( $json = json_decode( file_get_contents('php://input'), TRUE ) )
|
||||
{
|
||||
|
||||
+22
-11
@@ -99,7 +99,7 @@ class _Amazon extends \IPS\File
|
||||
static::$updatedEndpoint = NULL;
|
||||
}
|
||||
|
||||
if ( ! $values['toggle'] )
|
||||
if ( empty( $values['toggle'] ) )
|
||||
{
|
||||
$values['custom_url'] = NULL;
|
||||
}
|
||||
@@ -554,10 +554,12 @@ class _Amazon extends \IPS\File
|
||||
|
||||
$r = static::makeRequest( $path, 'POST', $this->configuration, $this->configurationId, NULL, NULL, FALSE, FALSE, array( 'uploads' => '' ) );
|
||||
$uploadId = (string) $r->decodeXml()->UploadId;
|
||||
|
||||
|
||||
return array(
|
||||
'path' => $path,
|
||||
'uploadId' => $uploadId
|
||||
'path' => $path,
|
||||
'uploadId' => $uploadId,
|
||||
'final' => $path,
|
||||
'original' => $this->originalFilename
|
||||
);
|
||||
}
|
||||
|
||||
@@ -584,15 +586,16 @@ class _Amazon extends \IPS\File
|
||||
|
||||
return $ref;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finalize a chunked upload
|
||||
*
|
||||
* @param mixed $ref The reference for this upload as returned by chunkInit()
|
||||
* @return string The file's reference which can be passed to \IPS\File::get() as the URL
|
||||
* @throws \RuntimeException
|
||||
* @param array $ref The reference for this upload as returned by chunkInit()
|
||||
* @param string $storageConfiguration Storage configuration name
|
||||
* @return \IPS\File The file object just created
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function chunkFinish( $ref )
|
||||
public function chunkFinish( array $ref, string $storageConfiguration ): \IPS\File
|
||||
{
|
||||
$xml = \IPS\Xml\SimpleXML::create( 'CompleteMultipartUpload', 'http://s3.amazonaws.com/doc/2006-03-01/' );
|
||||
foreach ( $ref['etags'] as $partNumber => $eTag )
|
||||
@@ -601,10 +604,18 @@ class _Amazon extends \IPS\File
|
||||
$part->addChild( 'ETag', $eTag );
|
||||
$part->addChild( 'PartNumber', $partNumber );
|
||||
}
|
||||
|
||||
|
||||
static::makeRequest( $ref['path'], 'POST', $this->configuration, $this->configurationId, $xml->asXML(), NULL, FALSE, NULL, array( 'uploadId' => $ref['uploadId'] ) );
|
||||
|
||||
return $ref['path'];
|
||||
$fileObj = \IPS\File::get( $storageConfiguration, $ref['final'] );
|
||||
|
||||
/* This isn't preserved for chunk uploads, so we need to set it so apps can get the real value */
|
||||
if ( !empty( $ref['original'] ) )
|
||||
{
|
||||
$fileObj->originalFilename = $ref['original'];
|
||||
}
|
||||
|
||||
return $fileObj;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -950,7 +950,11 @@ abstract class _File
|
||||
/* Make sure name doesn't have anything that may break URLs */
|
||||
if ( preg_match( '#[^a-zA-Z0-9!\-_\.\*\(\)\@]#', $filename ) )
|
||||
{
|
||||
$filename = mt_rand() . '_' . preg_replace( '#[^a-zA-Z0-9!\-_\.\*\(\)\@]#', '', $filename );
|
||||
$filename = preg_replace( '#[^a-zA-Z0-9!\-_\.\*\(\)\@]#', '', $filename );
|
||||
if( !$obscure OR \strlen( pathinfo( $filename, PATHINFO_FILENAME ) ) === 0 )
|
||||
{
|
||||
$filename = mt_rand() . '_' . $filename;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $obscure )
|
||||
|
||||
@@ -469,7 +469,7 @@ class _FileSystem extends \IPS\File
|
||||
|
||||
$this->setFilename( $filename, $obscure );
|
||||
|
||||
$return = array( 'temp' => "{$this->configuration['dir']}/{$tempFolder}/chunk-" . md5( uniqid() ) . '.txt', 'final' => "{$folder}/" . $this->filename );
|
||||
$return = array( 'temp' => "{$this->configuration['dir']}/{$tempFolder}/chunk-" . md5( uniqid() ) . '.txt', 'final' => "{$folder}/" . $this->filename, 'original' => $this->originalFilename );
|
||||
touch( $return['temp'] );
|
||||
return $return;
|
||||
}
|
||||
@@ -509,18 +509,28 @@ class _FileSystem extends \IPS\File
|
||||
|
||||
return $ref;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finalize a chunked upload
|
||||
*
|
||||
* @param mixed $ref The reference for this upload as returned by chunkInit()
|
||||
* @return string The file's reference which can be passed to \IPS\File::get() as the URL
|
||||
* @throws \RuntimeException
|
||||
* @param array $ref The reference for this upload as returned by chunkInit()
|
||||
* @param string $storageConfiguration Storage configuration name
|
||||
* @return \IPS\File The file object just created
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function chunkFinish( $ref )
|
||||
public function chunkFinish( array $ref, string $storageConfiguration ): \IPS\File
|
||||
{
|
||||
rename( $ref['temp'], "{$this->configuration['dir']}/{$ref['final']}" );
|
||||
return $ref['final'];
|
||||
|
||||
$fileObj = \IPS\File::get( $storageConfiguration,$ref['final'] );
|
||||
|
||||
/* This isn't preserved for chunk uploads, so we need to set it so apps can get the real value */
|
||||
if( !empty( $ref['original'] ) )
|
||||
{
|
||||
$fileObj->originalFilename = $ref['original'];
|
||||
}
|
||||
|
||||
return $fileObj;
|
||||
}
|
||||
|
||||
/* !File System Utility Methods */
|
||||
|
||||
@@ -94,7 +94,7 @@ class _Iterator extends \IteratorIterator implements \Countable
|
||||
}
|
||||
}
|
||||
|
||||
$fileSize = ( $this->fileSizeField AND !empty( $data[ $this->fileSizeField ] ) ) ? $data[ $this->fileSizeField ] : ( $this->fileSizeField === FALSE ? FALSE : NULL );
|
||||
$fileSize = ( $this->fileSizeField AND isset( $data[ $this->fileSizeField ] ) ) ? $data[ $this->fileSizeField ] : ( $this->fileSizeField === FALSE ? FALSE : NULL );
|
||||
$obj = \IPS\File::get( $this->storageExtension, $urlField ? $data[ $urlField ] : $data, $fileSize );
|
||||
|
||||
if ( $this->replaceNameField and ! empty( $data[ $this->replaceNameField ] ) )
|
||||
|
||||
@@ -39,6 +39,7 @@ class _CheckboxSet extends Select
|
||||
{
|
||||
$options['multiple'] = TRUE;
|
||||
$options['showAllNone'] = $options['showAllNone'] ?? TRUE;
|
||||
$options['condense'] = $options['condense'] ?? TRUE;
|
||||
parent::__construct( $name, $defaultValue, $required, $options, $customValidationCode, $prefix, $suffix, $id );
|
||||
|
||||
if ( !isset( $this->options['descriptions'] ) )
|
||||
@@ -93,7 +94,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, $this->options['impliedUnlimited'], $this->options['showAllNone'] );
|
||||
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'], $this->options['showAllNone'], $this->options['condense'] );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -290,15 +290,35 @@ class _Date extends FormAbstract
|
||||
}
|
||||
|
||||
/* Check minimum */
|
||||
|
||||
try
|
||||
{
|
||||
$timezone = $this->options['timezone'] ?: ( \IPS\Member::loggedIn()->timezone ? new \DateTimeZone( \IPS\Member::loggedIn()->timezone ) : NULL );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
$timezone = NULL;
|
||||
}
|
||||
|
||||
if ( $this->value and $this->options['min'] !== NULL and $this->options['min'] > $this->value )
|
||||
{
|
||||
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack('form_date_min', FALSE, array( 'sprintf' => array( $this->options['min']->localeDate() ) ) ) );
|
||||
$string = $this->options['min']->setTimeZone( $timezone )->localeDate( \IPS\Member::loggedIn() );
|
||||
if( $this->options['time'] )
|
||||
{
|
||||
$string .=' ' . $this->options['min']->setTimeZone( $timezone )->localeTime( \IPS\Member::loggedIn() );
|
||||
}
|
||||
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack('form_date_min', FALSE, array( 'sprintf' => array( $string ) ) ) );
|
||||
}
|
||||
|
||||
/* Check maximum */
|
||||
if ( $this->value and $this->options['max'] !== NULL and $this->options['max'] < $this->value )
|
||||
{
|
||||
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack('form_date_max', FALSE, array( 'sprintf' => array( $this->options['max']->localeDate() ) ) ) );
|
||||
$string = $this->options['max']->setTimeZone( $timezone )->localeDate( \IPS\Member::loggedIn() );
|
||||
if( $this->options['time'] )
|
||||
{
|
||||
$string .=' ' . $this->options['max']->setTimeZone( $timezone )->localeTime( \IPS\Member::loggedIn() );
|
||||
}
|
||||
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack('form_date_max', FALSE, array( 'sprintf' => array( $string ) ) ) );
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
|
||||
@@ -95,8 +95,11 @@ class _DateRange extends FormAbstract
|
||||
*/
|
||||
public function __construct( $name, $defaultValue=NULL, $required=FALSE, $options=array(), $customValidationCode=NULL, $prefix=NULL, $suffix=NULL, $id=NULL )
|
||||
{
|
||||
$this->start = new \IPS\Helpers\Form\Date( "{$name}[start]", isset( $defaultValue['start'] ) ? $defaultValue['start'] : NULL, FALSE, isset( $options['start'] ) ? $options['start'] : array() );
|
||||
$this->end = new \IPS\Helpers\Form\Date( "{$name}[end]", isset( $defaultValue['end'] ) ? $defaultValue['end'] : NULL, FALSE, isset( $options['end'] ) ? $options['end'] : array() );
|
||||
$startOptions = isset( $options['start'] ) ? array_merge( $this->defaultOptions['start'], $options['start'] ) : $this->defaultOptions['start'];
|
||||
$this->start = new \IPS\Helpers\Form\Date( "{$name}[start]", isset( $defaultValue['start'] ) ? $defaultValue['start'] : NULL, FALSE, $startOptions );
|
||||
|
||||
$endOptions = isset( $options['end'] ) ? array_merge( $this->defaultOptions['end'], $options['end'] ) : $this->defaultOptions['end'];
|
||||
$this->end = new \IPS\Helpers\Form\Date( "{$name}[end]", isset( $defaultValue['end'] ) ? $defaultValue['end'] : NULL, FALSE, $endOptions );
|
||||
$this->unlimitedName = "{$name}_unlimited";
|
||||
|
||||
parent::__construct( $name, $defaultValue, $required, $options, $customValidationCode, $prefix, $suffix, $id );
|
||||
|
||||
@@ -27,6 +27,7 @@ class _Poll extends FormAbstract
|
||||
* @code
|
||||
$defaultOptions = array(
|
||||
'allowPollOnly' => TRUE, // Controls if "Allow poll only" option is allowed (if the admin has enabled it)
|
||||
'itemClass' => NULL, // The content item class attached to this poll
|
||||
);
|
||||
* @endcode
|
||||
*/
|
||||
@@ -103,8 +104,13 @@ EOF;
|
||||
{
|
||||
$poll = new \IPS\Poll;
|
||||
$poll->starter_id = \IPS\Member::loggedIn()->member_id;
|
||||
|
||||
if( isset( $this->options['itemClass'] ) )
|
||||
{
|
||||
$poll->poll_item_class = $this->options['itemClass'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Set values */
|
||||
$data = parent::getValue();
|
||||
if ( !isset( $data['questions'] ) )
|
||||
|
||||
@@ -232,9 +232,7 @@ class _Upload extends FormAbstract
|
||||
/* If this is the LAST chunk, finish the process */
|
||||
if ( \IPS\Request::i()->chunk == \IPS\Request::i()->chunks - 1 )
|
||||
{
|
||||
$url = $storageClass->chunkFinish( $ref );
|
||||
|
||||
$fileObj = \IPS\File::get( $this->options['storageExtension'], $url );
|
||||
$fileObj = $storageClass->chunkFinish( $ref, $this->options['storageExtension'] );
|
||||
|
||||
$fileArray = array(
|
||||
'error' => NULL,
|
||||
@@ -247,24 +245,21 @@ class _Upload extends FormAbstract
|
||||
/* If there is an error, an exception will be thrown and will be caught below like normal */
|
||||
\IPS\File::validateUpload( $fileArray, $this->options['allowedFileTypes'], $this->options['maxFileSize'] );
|
||||
|
||||
if ( $fileObj->isImage() AND ( $this->options['checkImage'] OR \is_array( $this->options['image'] ) ) )
|
||||
if ( $fileObj->isImage() )
|
||||
{
|
||||
try
|
||||
{
|
||||
$image = \IPS\Image::create( $fileObj->contents() );
|
||||
$image = \IPS\Image::create( $fileObj->contents(), $this->options['checkImage'] ?: TRUE );
|
||||
|
||||
if( $image::exifSupported() )
|
||||
{
|
||||
$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 );
|
||||
}
|
||||
|
||||
if ( $hasBeenResized or $image->hasBeenRotated )
|
||||
|
||||
$hasBeenResized = $image->resizeToMax( $this->options['image']['maxWidth'] ?? NULL, $this->options['image']['maxHeight'] ?? NULL );
|
||||
|
||||
/* If type JPG, image handler may strip meta data on image output */
|
||||
if( $hasBeenResized OR $image->hasBeenRotated OR $image->type == 'jpeg' )
|
||||
{
|
||||
$fileObj->replace( (string) $image );
|
||||
}
|
||||
@@ -487,7 +482,7 @@ class _Upload extends FormAbstract
|
||||
{
|
||||
/* Get the files we had already */
|
||||
$return = array();
|
||||
$tempFiles = iterator_to_array( \IPS\Db::i()->select( 'id,contents,filename,exif,requires_moderation', 'core_files_temp', array( 'upload_key=?', md5( $this->name . session_id() ) ) )->setKeyField('id') );
|
||||
$tempFiles = iterator_to_array( \IPS\Db::i()->select( 'id,contents,filename,exif,requires_moderation,labels', 'core_files_temp', array( 'upload_key=?', md5( $this->name . session_id() ) ) )->setKeyField('id') );
|
||||
if ( $this->options['storageExtension'] )
|
||||
{
|
||||
$existingName = $this->name . '_existing';
|
||||
@@ -507,6 +502,7 @@ class _Upload extends FormAbstract
|
||||
$file->originalFilename = $tempFiles[ $tempId ]['filename'];
|
||||
$file->exifData = $tempFiles[ $tempId ]['exif'] ? json_decode( $tempFiles[ $tempId ]['exif'], TRUE ) : NULL;
|
||||
$file->requiresModeration = (bool) $tempFiles[ $tempId ]['requires_moderation'];
|
||||
$file->labels = $tempFiles[ $tempId ]['labels'];
|
||||
$return[ $id ] = $file;
|
||||
}
|
||||
else
|
||||
@@ -735,25 +731,19 @@ class _Upload extends FormAbstract
|
||||
{
|
||||
/* Resize */
|
||||
$image = NULL;
|
||||
$hasBeenResized = FALSE;
|
||||
if ( \is_array( $options['image'] ) OR $options['checkImage'] )
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
$image = \IPS\Image::create( $contents, $options['checkImage'] ?: TRUE );
|
||||
$hasBeenResized = $image->resizeToMax( $options['image']['maxWidth'] ?? NULL, $options['image']['maxHeight'] ?? NULL );
|
||||
|
||||
/* If type JPG, image handler may strip meta data on image output */
|
||||
if( $hasBeenResized OR $image->hasBeenRotated OR $image->type == 'jpeg' )
|
||||
{
|
||||
$image = \IPS\Image::create( $contents );
|
||||
|
||||
if( \is_array( $options['image'] ) )
|
||||
{
|
||||
$hasBeenResized = $image->resizeToMax( $options['image']['maxWidth'] ?: NULL, $options['image']['maxHeight'] ?: NULL );
|
||||
}
|
||||
|
||||
if ( $hasBeenResized or $image->hasBeenRotated )
|
||||
{
|
||||
$contents = (string) $image;
|
||||
}
|
||||
$contents = (string) $image;
|
||||
}
|
||||
catch ( \Exception $e ) {}
|
||||
}
|
||||
catch ( \Exception $e ) {}
|
||||
}
|
||||
|
||||
return $contents;
|
||||
|
||||
@@ -253,6 +253,10 @@ class _Content extends Table
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure we initialize the default sort fields so that we can properly get the column sort direction */
|
||||
$this->defaultSortBy = $this->sortBy;
|
||||
$this->defaultSortDirection = $this->sortDirection;
|
||||
|
||||
/* Do any multi-mod */
|
||||
if ( isset( \IPS\Request::i()->modaction ) )
|
||||
{
|
||||
@@ -1061,4 +1065,24 @@ class _Content extends Table
|
||||
{
|
||||
return $this->container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sort direction to use for links
|
||||
*
|
||||
* @note Abstracted so other table helper instances can adjust as needed
|
||||
* @param string $column Sort by string
|
||||
* @return string [asc|desc]
|
||||
*/
|
||||
public function getSortDirection( $column )
|
||||
{
|
||||
$class = $this->class;
|
||||
|
||||
/* ID and Title columns should be ascending */
|
||||
if( $column != $this->defaultSortBy AND ( $column == $class::$databaseColumnId OR $column == $class::$databaseColumnMap[ 'title' ] ) )
|
||||
{
|
||||
return 'asc';
|
||||
}
|
||||
|
||||
return parent::getSortDirection( $column );
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,16 @@ abstract class _Table
|
||||
* @brief Columns that are not sortable
|
||||
*/
|
||||
public $noSort = array();
|
||||
|
||||
/**
|
||||
* @brief Default column to sort results by
|
||||
*/
|
||||
public $defaultSortBy;
|
||||
|
||||
/**
|
||||
* @brief Default sort direction - "asc" or "desc"
|
||||
*/
|
||||
public $defaultSortDirection = NULL;
|
||||
|
||||
/**
|
||||
* @brief Filters
|
||||
@@ -770,6 +780,12 @@ abstract class _Table
|
||||
*/
|
||||
public function getSortDirection( $column )
|
||||
{
|
||||
/* If the column we are sorting by is the default sort, use the default sort direction */
|
||||
if ( $this->defaultSortBy AND $column == $this->defaultSortBy AND $this->defaultSortDirection )
|
||||
{
|
||||
return $this->defaultSortDirection;
|
||||
}
|
||||
|
||||
if( $column == 'title' )
|
||||
{
|
||||
return 'asc';
|
||||
|
||||
@@ -55,12 +55,13 @@ abstract class _Image
|
||||
/**
|
||||
* Create Object
|
||||
*
|
||||
* @param string $contents Contents
|
||||
* @param string $contents Contents
|
||||
* @param bool $checkRotation Check image to correct rotation
|
||||
* @return \IPS\Image
|
||||
* @throws \InvalidArgumentException
|
||||
* @link https://en.wikipedia.org/wiki/List_of_file_signatures
|
||||
*/
|
||||
public static function create( $contents )
|
||||
public static function create( string $contents, bool $checkRotation=TRUE )
|
||||
{
|
||||
/* Work out the type */
|
||||
$imageType = NULL;
|
||||
@@ -127,7 +128,7 @@ abstract class _Image
|
||||
}
|
||||
|
||||
/* Set EXIF data immediately */
|
||||
if( static::exifSupported() )
|
||||
if( static::exifSupported() AND $checkRotation )
|
||||
{
|
||||
$obj->setExifData( $contents );
|
||||
|
||||
|
||||
@@ -158,35 +158,6 @@ class _Imagemagick extends \IPS\Image
|
||||
$this->setDimensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize to maximum
|
||||
*
|
||||
* @param int|NULL $maxWidth Max Width (in pixels) or NULL
|
||||
* @param int|NULL $maxHeight Max Height (in pixels) or NULL
|
||||
* @param bool $retainRatio If TRUE, the image will keep it's current width/height ratio rather than being squashed
|
||||
* @return bool Returns TRUE if it actually resized, or FALSE if it wasn't necessary
|
||||
*/
|
||||
public function resizeToMax( $maxWidth=NULL, $maxHeight=NULL, $retainRatio=TRUE )
|
||||
{
|
||||
/* If we want to strip exif, we need to process this image regardless of size */
|
||||
if( ! \IPS\Settings::i()->imagick_strip_exif )
|
||||
{
|
||||
return parent::resizeToMax( $maxWidth, $maxHeight, $retainRatio );
|
||||
}
|
||||
|
||||
/* If the image is smaller, let's "resize" it keeping the same dimensions, so it runs via the __toString method */
|
||||
if( ( $maxWidth == NULL or $this->width < $maxWidth ) and ( $maxHeight == NULL or $this->height < $maxHeight ) )
|
||||
{
|
||||
$this->resize( $this->width, $this->height );
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return parent::resizeToMax( $maxWidth, $maxHeight, $retainRatio );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crop to a given width and height (will attempt to downsize first)
|
||||
*
|
||||
|
||||
@@ -1997,11 +1997,11 @@ class _Lang extends \IPS\Node\Model
|
||||
{
|
||||
if( isset( $values['options']['jsonEscape'] ) )
|
||||
{
|
||||
$replacement = mb_substr( json_encode( $replacement, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS ), 1, -1 );
|
||||
$replacement = mb_substr( json_encode( $replacement, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_UNESCAPED_UNICODE ), 1, -1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
$replacement = mb_substr( json_encode( $replacement, JSON_UNESCAPED_UNICODE ), 1, -1 );
|
||||
$replacement = mb_substr( json_encode( $replacement ), 1, -1 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -191,6 +191,15 @@ class _Handler extends \IPS\MFA\MFAHandler
|
||||
*/
|
||||
public function toggle( $enabled )
|
||||
{
|
||||
if( $enabled )
|
||||
{
|
||||
/* Check we have some questions */
|
||||
if ( !count( \IPS\MFA\SecurityQuestions\Question::roots() ) )
|
||||
{
|
||||
throw new \DomainException( 'no_questions' );
|
||||
}
|
||||
}
|
||||
|
||||
\IPS\Settings::i()->changeValues( array( 'security_questions_enabled' => $enabled ) );
|
||||
}
|
||||
|
||||
@@ -222,7 +231,9 @@ class _Handler extends \IPS\MFA\MFAHandler
|
||||
), NULL, NULL, NULL, 'security_questions_groups' ) );
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'security_questions_prompt', \IPS\Settings::i()->security_questions_prompt, FALSE, array( 'options' => array( 'register' => 'security_questions_prompt_register', 'optional' => 'security_questions_prompt_optional', 'access' => 'security_questions_prompt_access' ) ), NULL, NULL, NULL, 'security_questions_prompt' ) );
|
||||
$form->add( new \IPS\Helpers\Form\Number( 'security_questions_number', \IPS\Settings::i()->security_questions_number, FALSE, array( 'min' => 1, 'max' => \IPS\Db::i()->select( 'COUNT(*)', 'core_security_questions' )->first() ?: NULL ), NULL, NULL, NULL, 'security_questions_number' ) );
|
||||
|
||||
|
||||
$form->addMessage('nexus_mfa_reset_answers');
|
||||
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
$values['security_questions_groups'] = ( $values['security_questions_groups'] == '*' ) ? '*' : implode( ',', $values['security_questions_groups'] );
|
||||
@@ -352,7 +363,7 @@ class _Handler extends \IPS\MFA\MFAHandler
|
||||
* If member has configured this handler, disable it
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
* @return void
|
||||
*/
|
||||
public function disableHandlerForMember( \IPS\Member $member )
|
||||
{
|
||||
|
||||
@@ -728,7 +728,7 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
}
|
||||
else if( !$new AND isset( $this->changed['approved'] ) )
|
||||
{
|
||||
\IPS\Api\Webhook::fire( 'club_created', $this );
|
||||
$this->onApprove();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@@ -1364,17 +1364,25 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->cachedNodes[ $row['id'] ] = array(
|
||||
|
||||
try
|
||||
{
|
||||
$this->cachedNodes[ $row['id'] ] = array(
|
||||
'name' => $row['name'],
|
||||
'url' => \IPS\Http\Url::internal( $class::$urlBase . $row['node_id'], 'front', $class::$urlTemplate, array( \IPS\Http\Url\Friendly::seoTitle( $row['name'] ) ) ),
|
||||
'url' => $row['node_class']::load( $row['node_id'] )->url(),
|
||||
'node_class' => $row['node_class'],
|
||||
'node_id' => $row['node_id'],
|
||||
'public' => $row['public']
|
||||
);
|
||||
);
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
\IPS\Log::log( 'Missing club node ' . $row['node_class'] . ' ' . $row['node_id'] . " is being loaded.", 'club_nodes');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $this->cachedNodes;
|
||||
}
|
||||
|
||||
@@ -2172,4 +2180,15 @@ class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddabl
|
||||
|
||||
\IPS\Db::i()->update( 'core_clubs_memberships', array( 'rules_acknowledged' => TRUE ), array( "club_id=? AND member_id=?", $this->id, $member->member_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a club requiring moderation gets approved
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onApprove()
|
||||
{
|
||||
\IPS\Api\Webhook::fire( 'club_created', $this );
|
||||
$this->owner->achievementAction( 'core', 'NewClub', $this );
|
||||
}
|
||||
}
|
||||
@@ -157,6 +157,12 @@ class _Page extends \IPS\Patterns\ActiveRecord
|
||||
'moderator' => 'club_page_moderators'
|
||||
) ) ) );
|
||||
}
|
||||
|
||||
/* Add the index setting if this page is shown to guests */
|
||||
if ( $club->type !== \IPS\Member\Club::TYPE_PRIVATE AND \IPS\Member::loggedIn()->group['gbw_club_manage_indexing'] )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\YesNo('club_page_meta_index', ( $current ) ? $current->meta_index : TRUE, FALSE, [], NULL, NULL, NULL, 'club_page_meta_index' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,6 +184,10 @@ class _Page extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$this->can_view = NULL;
|
||||
}
|
||||
if( array_key_exists( 'club_page_meta_index', $values ) )
|
||||
{
|
||||
$this->meta_index = $values['club_page_meta_index'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -86,6 +86,7 @@ class _Group extends \IPS\Patterns\ActiveRecord
|
||||
'gbw_hide_inline_modevents' => 128, // Users in this group can see inline moderation events (where supported)
|
||||
'gbw_posted_in' => 256, // Items (topics, articles) will be marked as 'posted in by this group'
|
||||
'gbw_posted_in_secondary' => 512, // Items (topics, articles) will be marked as 'posted in by this group' when the group is set as a secondary group
|
||||
'gbw_club_manage_indexing' => 1024, // Users in this group can manage indexing of clubs
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -119,6 +119,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
'datalayer_event_fired' => 8388608,
|
||||
'new_device_email' => 16777216, // Send an email when a new device is used to log in.
|
||||
'no_solved_reenage' => 33554432, // User does not want to get topic re-engagement emails
|
||||
'datalayer_pii_optout' => 67108864, // User has opted in to having their PII collected by datalayer
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -202,7 +203,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
if ( !static::$loggedInMember->member_id and isset( \IPS\Request::i()->cookie['ipsTimezone'] ) )
|
||||
{
|
||||
static::$loggedInMember->timezone = \IPS\Request::i()->cookie['ipsTimezone'];
|
||||
static::$loggedInMember->timezone = \IPS\DateTime::getFixedTimezone( \IPS\Request::i()->cookie['ipsTimezone'] );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3508,7 +3509,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
$return['reputationPoints'] = $this->pp_reputation_points;
|
||||
$return['photoUrl'] = static::photoUrl( $this->_data, FALSE );
|
||||
$return['photoUrlIsDefault'] = $this->pp_photo_type == 'none' or static::photoUrl( $this->_data, FALSE, FALSE, FALSE ) != $return['photoUrl'];
|
||||
$return['photoUrlIsDefault'] = \in_array( $this->pp_photo_type, [ 'none', 'letter' ] ) or static::photoUrl( $this->_data, FALSE, FALSE, FALSE ) != $return['photoUrl'];
|
||||
$return['coverPhotoUrl'] = $this->pp_cover_photo ? ( (string) \IPS\File::get( 'core_Profile', $this->pp_cover_photo )->url ) : '';
|
||||
$return['profileUrl'] = ( $this->member_id ) ? (string) $this->url() : NULL;
|
||||
if ( !$authorizedMember )
|
||||
@@ -3524,7 +3525,10 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
$return['lastPost'] = $this->member_last_post ? \IPS\DateTime::ts( $this->member_last_post )->rfc3339() : NULL;
|
||||
}
|
||||
|
||||
$return['birthday'] = $this->bday_month ? ( $this->bday_month . '/' . $this->bday_day . ( $this->bday_year ? '/' . $this->bday_year : '' ) ) : NULL;
|
||||
if( !$authorizedMember OR $authorizedMember?->member_id === $this->member_id )
|
||||
{
|
||||
$return['birthday'] = $this->bday_month ? ( $this->bday_month . '/' . $this->bday_day . ( $this->bday_year ? '/' . $this->bday_year : '' ) ) : NULL;
|
||||
}
|
||||
$return['profileViews'] = $this->members_profile_views;
|
||||
$return['customFields'] = $fields;
|
||||
|
||||
@@ -3822,7 +3826,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public function recountReputation()
|
||||
{
|
||||
$this->pp_reputation_points = \IPS\Db::i()->select( 'SUM(rep_rating)', 'core_reputation_index', array( 'member_received=?', $this->member_id ) );
|
||||
$this->pp_reputation_points = \IPS\Db::i()->select( 'SUM(rep_rating)', 'core_reputation_index', array( 'member_received=?', $this->member_id ) )->first();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
@@ -5444,7 +5448,7 @@ class _Member extends \IPS\Patterns\ActiveRecord
|
||||
/* Are they enabled for our group? */
|
||||
$sigLimits = explode( ":", $this->group['g_signature_limits'] );
|
||||
|
||||
if( $sigLimits[0] )
|
||||
if( $sigLimits[0] OR $sigLimits[5] == 0 )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -199,4 +199,25 @@ class _NodeController extends \IPS\Api\Controller
|
||||
/* Output */
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the global available where condition for all nodes
|
||||
*
|
||||
* @param array|null $where Extra WHERE clause
|
||||
* @return array
|
||||
*/
|
||||
protected function _globalWhere(array $where = NULL): array
|
||||
{
|
||||
$class = $this->class;
|
||||
|
||||
$where = $where ?: [];
|
||||
$idField = $class::$databaseTable . '.' . $class::$databasePrefix . '.' . $class::$databaseColumnId;
|
||||
|
||||
if ( isset( \IPS\Request::i()->ids ) )
|
||||
{
|
||||
$where[] = array( \IPS\Db::i()->in( $idField, array_map( 'intval', explode(',', \IPS\Request::i()->ids ) ) ) );
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
}
|
||||
+11
-3
@@ -3783,10 +3783,18 @@ abstract class _Model extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$return['permissions'] = \in_array( 'IPS\Node\Permissions', class_implements( \get_class( $this ) ) ) ? $this->permissions() : NULL;
|
||||
}
|
||||
|
||||
if ( \IPS\IPS::classUsesTrait( \get_called_class(), 'IPS\Content\ClubContainer' ) AND $this->club() )
|
||||
|
||||
if ( \IPS\IPS::classUsesTrait( \get_called_class(), 'IPS\Content\ClubContainer' ) )
|
||||
{
|
||||
$return['public'] = ( $this->isPublic() ) ? 1 : 0;
|
||||
if( $this->club() )
|
||||
{
|
||||
$return['public'] = ( $this->isPublic() ) ? 1 : 0;
|
||||
$return['club'] = $this->club()->apiOutput( $authorizedMember );
|
||||
}
|
||||
else
|
||||
{
|
||||
$return['club'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
|
||||
@@ -1103,9 +1103,18 @@ class _Notification
|
||||
|
||||
$headers['Content-Length'] = \Minishlink\WebPush\Utils::safeStrlen($content);
|
||||
$headers['Authorization'] = $vapidHeaders['Authorization'];
|
||||
|
||||
// Step 8: Send the damn thing
|
||||
$response = $endpoint->request()->setHeaders( $headers )->post( $content );
|
||||
|
||||
try
|
||||
{
|
||||
// Step 8: Send the damn thing
|
||||
$response = $endpoint->request()->setHeaders( $headers )->post( $content );
|
||||
}
|
||||
catch( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
/* If the request failed (DNS issues, etc.). Log it and move on. */
|
||||
\IPS\Log::log( $e, 'pwa_notification' );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Step 9: Check the response - 404/410 indicates a permanent problem, so delete that key
|
||||
if( \in_array( $response->httpResponseCode, array( 404, 410 ) ) )
|
||||
|
||||
@@ -230,6 +230,11 @@ class _Output
|
||||
* @brief pageName for data-pageName in the <body> tag
|
||||
*/
|
||||
public $pageName = NULL;
|
||||
|
||||
/**
|
||||
* @brief Data which were loaded via the GraphQL framework, but which have to be immediately available, rather then via later AJAX requests
|
||||
*/
|
||||
public $graphData = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -259,12 +264,32 @@ class _Output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JS bundle and add the files to the output. This greatly cleans up the usage from outside of the Output class
|
||||
*
|
||||
* @par JS Bundle Filename Cheatsheet
|
||||
* @li library.js (this is jQuery, mustache, underscore, jstz, etc)
|
||||
* @li framework.js (this is ui/, utils/*, ips.model.js, ips.controller.js and the editor controllers)
|
||||
* @li admin.js or front.js (these are controllers, templates and models which are used everywhere for that location)
|
||||
* @li app.js (this is all models for a single application)
|
||||
* @li {location}_{section}.js (this is all controllers and templates for this section called ad-hoc when needed)
|
||||
*
|
||||
* @param string $file Filename
|
||||
* @param string|null $app Application
|
||||
* @param string|null $location Location (e.g. 'admin', 'front')
|
||||
* @return array URL to JS files
|
||||
*/
|
||||
public function addJsFiles( $file, $app=NULL, $location=NULL )
|
||||
{
|
||||
$this->jsFiles = array_merge( $this->jsFiles, $this->js( $file, $app, $location ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JS bundle
|
||||
*
|
||||
* @par JS Bundle Cheatsheet
|
||||
* @li library.js (this is jQuery, mustache, underscore, jstz, etc)
|
||||
* @li library.js (this is jQuery, mustache, underscore etc)
|
||||
* @li framework.js (this is ui/, utils/*, ips.model.js, ips.controller.js and the editor controllers)
|
||||
* @li admin.js or front.js (these are controllers, templates and models which are used everywhere for that location)
|
||||
* @li app.js (this is all models for a single application)
|
||||
@@ -808,6 +833,11 @@ class _Output
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( \defined('LIVE_TOPICS_DEV') AND \LIVE_TOPICS_DEV )
|
||||
{
|
||||
$httpHeaders['Access-Control-Allow-Origin'] = '*';
|
||||
}
|
||||
|
||||
/* Cache session Data Layer events */
|
||||
if ( !( $httpStatusCode === 200 AND !\IPS\Request::i()->isAjax() AND $contentType == 'text/html' ) )
|
||||
{
|
||||
|
||||
@@ -756,6 +756,7 @@ CODE;
|
||||
$inHereDoc = NULL;
|
||||
$inThemeHooks = FALSE;
|
||||
$inComments = FALSE;
|
||||
$inUseStatement = FALSE;
|
||||
|
||||
$fh = fopen( $file, 'r' );
|
||||
while ( $line = fgets( $fh ) )
|
||||
@@ -805,10 +806,16 @@ CODE;
|
||||
|
||||
if ( $depth == 2 and $closeBraces )
|
||||
{
|
||||
$contents .= "{$tabs}}\n{$tabs}catch ( \Error | \RuntimeException \$e )\n{$tabs}{\n{$tabs}\tif( \defined( '\IPS\DEBUG_HOOKS' ) AND \IPS\DEBUG_HOOKS )\n{$tabs}\t{\n{$tabs}\t\t\IPS\Log::log( \$e, 'hook_exception' );\n{$tabs}\t}\n\n{$tabs}\tif ( method_exists( get_parent_class(), __FUNCTION__ ) )\n{$tabs}\t{\n{$tabs}\t\treturn \\call_user_func_array( 'parent::' . __FUNCTION__, \\func_get_args() );\n{$tabs}\t}\n{$tabs}\telse\n{$tabs}\t{\n{$tabs}\t\tthrow \$e;\n{$tabs}\t}\n{$tabs}}\n";
|
||||
/* If we are in a USE statement, we don't want to do any wrapping */
|
||||
if ( !$inUseStatement )
|
||||
{
|
||||
$contents .= "{$tabs}}\n{$tabs}catch ( \Error | \RuntimeException \$e )\n{$tabs}{\n{$tabs}\tif( \defined( '\IPS\DEBUG_HOOKS' ) AND \IPS\DEBUG_HOOKS )\n{$tabs}\t{\n{$tabs}\t\t\IPS\Log::log( \$e, 'hook_exception' );\n{$tabs}\t}\n\n{$tabs}\tif ( method_exists( get_parent_class(), __FUNCTION__ ) )\n{$tabs}\t{\n{$tabs}\t\treturn \\call_user_func_array( 'parent::' . __FUNCTION__, \\func_get_args() );\n{$tabs}\t}\n{$tabs}\telse\n{$tabs}\t{\n{$tabs}\t\tthrow \$e;\n{$tabs}\t}\n{$tabs}}\n";
|
||||
}
|
||||
|
||||
$inUseStatement = FALSE;
|
||||
$depth--;
|
||||
}
|
||||
|
||||
|
||||
if ( !$inHereDoc and $depth > 2 )
|
||||
{
|
||||
$contents .= "\t";
|
||||
@@ -825,7 +832,11 @@ CODE;
|
||||
|
||||
if ( $depth == 2 and $openBraces )
|
||||
{
|
||||
$contents .= "{$tabs}try\n{$tabs}{\n";
|
||||
$inUseStatement = \substr( \trim( \strtolower( $line ) ), 0, 3 ) == 'use';
|
||||
if ( !$inUseStatement )
|
||||
{
|
||||
$contents .= "{$tabs}try\n{$tabs}{\n";
|
||||
}
|
||||
$depth++;
|
||||
}
|
||||
|
||||
|
||||
+23
-2
@@ -213,7 +213,7 @@ class _Poll extends \IPS\Patterns\ActiveRecord implements \SplSubject
|
||||
{
|
||||
$choices[ $k ] = array(
|
||||
'question' => $questionData['title'],
|
||||
'multi' => \intval( isset( $questionData['multichoice'] ) ),
|
||||
'multi' => (int) !empty( $questionData['multichoice'] ),
|
||||
'choice' => array(),
|
||||
'votes' => array(),
|
||||
);
|
||||
@@ -588,7 +588,7 @@ class _Poll extends \IPS\Patterns\ActiveRecord implements \SplSubject
|
||||
foreach ( $this->choices as $k => $data )
|
||||
{
|
||||
$class = ( isset( $data['multi'] ) AND $data['multi'] ) ? 'IPS\Helpers\Form\CheckboxSet' : 'IPS\Helpers\Form\Radio';
|
||||
$input = new $class( $k, ( isset( $data['multi'] ) AND $data['multi'] ) ? array() : NULL, TRUE, array( 'options' => $data['choice'], 'noDefault' => TRUE ) );
|
||||
$input = new $class( $k, ( isset( $data['multi'] ) AND $data['multi'] ) ? array() : NULL, TRUE, array( 'options' => $data['choice'], 'noDefault' => TRUE, 'showAllNone' => FALSE, 'condense' => FALSE ) );
|
||||
|
||||
$input->label = $data['question'];
|
||||
$form->add( $input );
|
||||
@@ -660,6 +660,10 @@ class _Poll extends \IPS\Patterns\ActiveRecord implements \SplSubject
|
||||
* @return array
|
||||
* @apiresponse int id ID number
|
||||
* @apiresponse string title Title
|
||||
* @apiresponse datetime startDate Start Date
|
||||
* @apiresponse bool closed Closed
|
||||
* @apiresponse datetime closedDate Closed Date
|
||||
* @apiresponse bool public Public poll
|
||||
* @apiresponse int votes Number of votes
|
||||
* @apiresponse [\IPS\Poll\Question] questions The questions
|
||||
*/
|
||||
@@ -674,6 +678,10 @@ class _Poll extends \IPS\Patterns\ActiveRecord implements \SplSubject
|
||||
return array(
|
||||
'id' => $this->pid,
|
||||
'title' => $this->poll_question,
|
||||
'startDate' => $this->start_date->rfc3339(),
|
||||
'closed' => (bool) $this->poll_closed,
|
||||
'closedDate' => ( $this->poll_closed ? $this->poll_close_date->rfc3339() : null ),
|
||||
'public' => (bool) $this->poll_view_voters,
|
||||
'votes' => $this->votes,
|
||||
'questions' => $questions
|
||||
);
|
||||
@@ -699,4 +707,17 @@ class _Poll extends \IPS\Patterns\ActiveRecord implements \SplSubject
|
||||
$this->author()->achievementAction( 'core', 'NewPoll', $this );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Delete Record
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
parent::delete();
|
||||
|
||||
/* Delete records from voters table */
|
||||
\IPS\Db::i()->delete( 'core_voters', array( 'poll=?', $this->pid ) );
|
||||
}
|
||||
}
|
||||
@@ -724,13 +724,13 @@ class _Request extends \IPS\Patterns\Singleton
|
||||
if ( \function_exists('apache_request_headers') )
|
||||
{
|
||||
$headers = @apache_request_headers();
|
||||
if ( isset( $headers['Authorization'] ) )
|
||||
$headerKeys = ['authorization', 'x-authorization'];
|
||||
foreach ( $headers as $k => $v )
|
||||
{
|
||||
return $headers['Authorization'];
|
||||
}
|
||||
elseif ( isset( $headers['X-Authorization'] ) )
|
||||
{
|
||||
return $headers['X-Authorization'];
|
||||
if ( \in_array( \mb_strtolower( $k ), $headerKeys ) )
|
||||
{
|
||||
return $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ class _Front extends \IPS\Session
|
||||
{
|
||||
$accessToken = \IPS\Api\OAuthClient::accessTokenDetails( mb_substr( $authorizationHeader, 7 ) );
|
||||
$client = \IPS\Api\OAuthClient::load( $accessToken['client_id'] );
|
||||
if ( $client->graphql and $accessToken['member_id'] === $expectedMember->member_id )
|
||||
if ( \in_array($client->api_access, [ 'graphql', 'both'] ) AND $accessToken['member_id'] === $expectedMember->member_id )
|
||||
{
|
||||
$success = TRUE;
|
||||
}
|
||||
|
||||
@@ -24,9 +24,10 @@ class _Sitemap
|
||||
{
|
||||
/**
|
||||
* @brief Maximum number of entries to include per file
|
||||
* @deprecated 4.7.8 This will be removed in the future. Use now \IPS\SITEMAP_MAX_PER_FILE
|
||||
*/
|
||||
const MAX_PER_FILE = 500;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Count options
|
||||
*/
|
||||
|
||||
@@ -1930,7 +1930,7 @@ class _Parser
|
||||
$file = $this->_getFile( 'core_Attachment', $element->getAttribute( $srcAttribute ) );
|
||||
|
||||
$element->setAttribute( 'data-fileid', $attachment['attach_id'] );
|
||||
$element->setAttribute( 'src', str_replace( array( 'http:', 'https:' ), '', str_replace( static::$fileObjectClasses['core_Attachment']->baseUrl(), '{fileStore.core_Attachment}', $element->getAttribute( $srcAttribute ) ) ) );
|
||||
$element->setAttribute( $srcAttribute, str_replace( array( 'http:', 'https:' ), '', str_replace( static::$fileObjectClasses['core_Attachment']->baseUrl(), '{fileStore.core_Attachment}', $element->getAttribute( $srcAttribute ) ) ) );
|
||||
|
||||
|
||||
if ( ( !$element->hasAttribute('alt') or $element->getAttribute('alt') == $file->filename ) and ! empty( $attachment['attach_labels'] ) and $labels = static::getAttachmentLabels( $attachment ) )
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @brief Theme Hook Compilation Skeleton - Catch calls to parent during compile.
|
||||
* @author <a href='https://invisioncommunity.com/'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2023 Invision Power Services, Inc.
|
||||
* @package Invision Community
|
||||
* @since 16 February 2023
|
||||
*/
|
||||
|
||||
namespace IPS\Theme\Compile;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme Hook skeleton class
|
||||
*/
|
||||
class _ThemeHook
|
||||
{
|
||||
/**
|
||||
* @param $name
|
||||
* @param $arguments
|
||||
* @return string
|
||||
*/
|
||||
public function __call( string $name, array $arguments ): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $arguments
|
||||
* @return string
|
||||
*/
|
||||
public static function __callStatic( string $name, array $arguments ): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return void
|
||||
*/
|
||||
public function __set( string $name, mixed $value ): void
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2700,7 +2700,7 @@ class _Theme extends \IPS\Node\Model
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( eval( "namespace IPS\\Theme;\n\n" . str_replace( array( ' extends _HOOK_CLASS_', 'parent::hookData()' ), array( '_tmp', 'array()' ), file_get_contents( $path . '/' . $data['file'] ) ) ) !== FALSE )
|
||||
if ( eval( "namespace IPS\\Theme;\n\n" . str_replace( array( ' extends _HOOK_CLASS_', 'parent::hookData()' ), array( '_tmp EXTENDS \IPS\Theme\Compile\ThemeHook', 'array()' ), file_get_contents( $path . '/' . $data['file'] ) ) ) !== FALSE )
|
||||
{
|
||||
$class = "IPS\\Theme\\" . $data['class'] . "_tmp";
|
||||
$templateHooks = array_merge_recursive( $templateHooks, $class::hookData() );
|
||||
|
||||
Reference in new issue
Block a user