Version 4.3.0

This commit is contained in:
Neo committed 2025-12-19 05:47:12 -08:00
1 parent 96997ddd8e
commit fe1acf984a
1349 files changed
+116159 -67711

No files matched your search

+209 -56
View File
@@ -32,6 +32,7 @@ class _files extends \IPS\Content\Api\ItemController
* GET /downloads/files
* Get list of files
*
* @note For requests using an OAuth Access Token for a particular member, only files the authorized user can view will be included
* @apiparam string categories Comma-delimited list of category IDs
* @apiparam string authors Comma-delimited list of member IDs - if provided, only files started by those members are returned
* @apiparam int locked If 1, only files which are locked are returned, if 0 only unlocked
@@ -41,6 +42,7 @@ class _files extends \IPS\Content\Api\ItemController
* @apiparam string sortBy What to sort by. Can be 'date' for creation date, 'title' or leave unspecified for ID
* @apiparam string sortDir Sort direction. Can be 'asc' or 'desc' - defaults to 'asc'
* @apiparam int page Page number
* @apiparam int perPage Number of results per page - defaults to 25
* @return \IPS\Api\PaginatedResponse<IPS\downloads\File>
*/
public function GETindex()
@@ -58,7 +60,7 @@ class _files extends \IPS\Content\Api\ItemController
*
* @param int $id ID Number
* @apiparam int version If specified, will show a previous version of a file (see GET /downloads/files/{id}/versions)
* @throws 2S303/1 INVALID_ID The file ID does not exist
* @throws 2S303/1 INVALID_ID The file ID does not exist or the authorized user does not have permission to view it
* @throws 2S303/1 INVALID_VERSION The version ID does not exist
* @return \IPS\downloads\File
*/
@@ -67,13 +69,17 @@ class _files extends \IPS\Content\Api\ItemController
try
{
$file = \IPS\downloads\File::load( $id );
if ( $this->member and !$file->can( 'read', $this->member ) )
{
throw new \OutOfRangeException;
}
if ( isset( \IPS\Request::i()->version ) )
{
try
{
$backup = \IPS\Db::i()->select( '*', 'downloads_filebackup', array( 'b_id=? AND b_fileid=?', \IPS\Request::i()->version, $file->id ) )->first();
return new \IPS\Api\Response( 200, $file->apiOutput( $backup ) );
return new \IPS\Api\Response( 200, $file->apiOutput( $this->member, $backup ) );
}
catch ( \UnderflowException $e )
{
@@ -82,7 +88,7 @@ class _files extends \IPS\Content\Api\ItemController
}
else
{
return new \IPS\Api\Response( 200, $file->apiOutput() );
return new \IPS\Api\Response( 200, $file->apiOutput( $this->member ) );
}
}
catch ( \OutOfRangeException $e )
@@ -95,17 +101,18 @@ class _files extends \IPS\Content\Api\ItemController
* POST /downloads/files
* Upload a file
*
* @note For requests using an OAuth Access Token for a particular member, any parameters the user doesn't have permission to use are ignored (for example, locked will only be honoured if the authentictaed user has permission to lock files).
* @reqapiparam int category The ID number of the calendar the file should be created in
* @reqapiparam int author The ID number of the member creating the file (0 for guest)
* @reqapiparam int author The ID number of the member creating the file (0 for guest). Required for requests made using an API Key or the Client Credentials Grant Type. For requests using an OAuth Access Token for a particular member, that member will always be the author
* @reqapiparam string title The file name
* @reqapiparam string description The description as HTML (e.g. "<p>This is an file.</p>")
* @reqapiparam string description The description as HTML (e.g. "<p>This is an file.</p>"). Will be sanatized for requests using an OAuth Access Token for a particular member; will be saved unaltered for requests made using an API Key or the Client Credentials Grant Type.
* @apiparam string version The version number
* @reqapiparam object files Files. Keys should be filename (e.g. 'file.txt') and values should be file content
* @apiparam object screenshots Screenshots. Keys should be filename (e.g. 'screenshot1.png') and values should be file content
* @apiparam object screenshots Screenshots. Keys should be filename (e.g. 'screenshot1.png') and values should be file content.
* @apiparam string prefix Prefix tag
* @apiparam string tags Comma-separated list of tags (do not include prefix)
* @apiparam datetime date The date/time that should be used for the file post date. If not provided, will use the current date/time
* @apiparam string ip_address The IP address that should be stored for the file. If not provided, will use the IP address from the API request
* @apiparam datetime date The date/time that should be used for the file post date. If not provided, will use the current date/time. Ignored for requests using an OAuth Access Token for a particular member.
* @apiparam string ip_address The IP address that should be stored for the file. If not provided, will use the IP address from the API request. Ignored for requests using an OAuth Access Token for a particular member.
* @apiparam int locked 1/0 indicating if the file should be locked
* @apiparam int hidden 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
* @apiparam int pinned 1/0 indicating if the file should be featured
@@ -115,6 +122,13 @@ class _files extends \IPS\Content\Api\ItemController
* @throws 1S303/9 NO_TITLE No title was supplied
* @throws 1S303/A NO_DESC No description was supplied
* @throws 1S303/B NO_FILES No files were supplied
* @throws 2S303/H NO_PERMISSION The authorized user does not have permission to create a file in that category
* @throws 1S303/I BAD_FILE_EXT One of the files has a file type that is not allowed
* @throws 1S303/J BAD_FILE_SIZE One of the files is too big
* @throws 1S303/K BAD_SS One of the screenshots is not a valid image
* @throws 1S303/L BAD_SS_SIZE One of the screenshots is too big by filesize
* @throws 1S303/M BAD_SS_DIMS One of the screenshots is too big by dimensions
* @throws 1S303/N NO_SS No screenshots are provided, but screenshots are required for the category
* @return \IPS\downloads\File
*/
public function POSTindex()
@@ -130,17 +144,28 @@ class _files extends \IPS\Content\Api\ItemController
}
/* Get author */
if ( \IPS\Request::i()->author )
if ( $this->member )
{
$author = \IPS\Member::load( \IPS\Request::i()->author );
if ( !$author->member_id )
if ( !$category->can( 'add', $this->member ) )
{
throw new \IPS\Api\Exception( 'NO_AUTHOR', '1S303/8', 400 );
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2S303/H', 403 );
}
$author = $this->member;
}
else
{
$author = new \IPS\Member;
if ( \IPS\Request::i()->author )
{
$author = \IPS\Member::load( \IPS\Request::i()->author );
if ( !$author->member_id )
{
throw new \IPS\Api\Exception( 'NO_AUTHOR', '1S303/8', 400 );
}
}
else
{
$author = new \IPS\Member;
}
}
/* Check we have a title and a description */
@@ -158,14 +183,18 @@ class _files extends \IPS\Content\Api\ItemController
{
throw new \IPS\Api\Exception( 'NO_FILES', '1L296/B', 400 );
}
if ( $this->member )
{
$this->_validateFilesForMember( $category );
}
/* Create file record */
$file = $this->_create( $category, $author );
/* Save records */
foreach ( \IPS\Request::i()->files as $name => $content )
foreach ( array_keys( \IPS\Request::i()->files ) as $name )
{
$fileObject = \IPS\File::create( 'downloads_Files', $name, $content );
$fileObject = \IPS\File::create( 'downloads_Files', $name, $_POST['files'][ $name ] );
\IPS\Db::i()->insert( 'downloads_files_records', array(
'record_file_id' => $file->id,
@@ -176,12 +205,12 @@ class _files extends \IPS\Content\Api\ItemController
'record_time' => time(),
) );
}
if ( isset( \IPS\Request::i()->screenshots ) )
if ( $category->bitoptions['allowss'] and isset( \IPS\Request::i()->screenshots ) )
{
$primary = 1;
foreach ( \IPS\Request::i()->screenshots as $name => $content )
foreach ( array_keys( \IPS\Request::i()->screenshots ) as $name )
{
$fileObject = \IPS\File::create( 'downloads_Screenshots', $name, $content );
$fileObject = \IPS\File::create( 'downloads_Screenshots', $name, $_POST['screenshots'][ $name ] );
\IPS\Db::i()->insert( 'downloads_files_records', array(
'record_file_id' => $file->id,
@@ -204,27 +233,90 @@ class _files extends \IPS\Content\Api\ItemController
/* Return */
$file->save();
return new \IPS\Api\Response( 201, $file->apiOutput() );
return new \IPS\Api\Response( 201, $file->apiOutput( $this->member ) );
}
/**
* Validate that the authorized member can upload the files provided
*
* @param \IPS\downloads\Category $category The category
* @return void
*/
protected function _validateFilesForMember( $category )
{
foreach ( \IPS\Request::i()->files as $name => $content )
{
if ( $category->types )
{
$ext = mb_substr( $name, mb_strrpos( $name, '.' ) + 1 );
if( !in_array( mb_strtolower( $ext ), array_map( 'mb_strtolower', $category->types ) ) )
{
throw new \IPS\Api\Exception( 'BAD_FILE_EXT', '1S303/I', 400 );
}
}
if ( $category->maxfile and \strlen( $content ) > ( $category->maxfile * 1024 ) )
{
throw new \IPS\Api\Exception( 'BAD_FILE_SIZE', '1S303/J', 400 );
}
}
if ( $category->bitoptions['allowss'] )
{
if ( isset( \IPS\Request::i()->screenshots ) and \IPS\Request::i()->screenshots )
{
foreach ( \IPS\Request::i()->screenshots as $name => $content )
{
if ( $category->maxss and \strlen( $content ) > ( $category->maxss * 1024 ) )
{
throw new \IPS\Api\Exception( 'BAD_SS_SIZE', '1S303/L', 400 );
}
try
{
$image = \IPS\Image::create( $content );
if ( $category->maxdims )
{
$maxDims = explode( 'x', $category->maxdims );
if ( $image->width > $maxDims[0] or $image->height > $maxDims[1] )
{
throw new \IPS\Api\Exception( 'BAD_SS_DIMS', '1S303/M', 400 );
}
}
}
catch ( \InvalidArgumentException $e )
{
throw new \IPS\Api\Exception( 'BAD_SS', '1S303/K', 400 );
}
}
}
elseif ( $category->bitoptions['reqss'] )
{
throw new \IPS\Api\Exception( 'NO_SS', '1S303/N', 400 );
}
}
}
/**
* POST /downloads/files/{id}
* Edit a file
*
* @note For requests using an OAuth Access Token for a particular member, any parameters the user doesn't have permission to use are ignored (for example, locked will only be honoured if the authenticated user has permission to lock records).
* @apiparam int category The ID number of the calendar the file should be created in
* @apiparam int author The ID number of the member creating the file (0 for guest)
* @apiparam int author The ID number of the member creating the file (0 for guest). Ignored for requests using an OAuth Access Token for a particular member.
* @apiparam string title The file name
* @apiparam string description The description as HTML (e.g. "<p>This is an file.</p>")
* @apiparam string description The description as HTML (e.g. "<p>This is an file.</p>"). Will be sanatized for requests using an OAuth Access Token for a particular member; will be saved unaltered for requests made using an API Key or the Client Credentials Grant Type.
* @apiparam string prefix Prefix tag
* @apiparam string tags Comma-separated list of tags (do not include prefix)
* @apiparam datetime date The date/time that should be used for the file post date. If not provided, will use the current date/time
* @apiparam string ip_address The IP address that should be stored for the file. If not provided, will use the IP address from the API request
* @apiparam datetime date The date/time that should be used for the file post date. If not provided, will use the current date/time. Ignored for requests using an OAuth Access Token for a particular member.
* @apiparam string ip_address The IP address that should be stored for the file. If not provided, will use the IP address from the API request. Ignored for requests using an OAuth Access Token for a particular member.
* @apiparam int locked 1/0 indicating if the file should be locked
* @apiparam int hidden 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
* @apiparam int featured 1/0 indicating if the file should be featured
* @throws 2S303/C INVALID_ID The file ID is invalid
* @throws 1S303/D NO_CATEGORY The category ID does not exist
* @throws 2S303/C INVALID_ID The file ID is invalid or the authorized user does not have permission to view it
* @throws 1S303/D NO_CATEGORY The category ID does not exist or the authorized user does not have permission to post in it
* @throws 1S303/E NO_AUTHOR The author ID does not exist
* @throws 2S303/O NO_PERMISSION The authorized user does not have permission to edit the file
* @return \IPS\downloads\File
*/
public function POSTitem( $id )
@@ -232,13 +324,26 @@ class _files extends \IPS\Content\Api\ItemController
try
{
$file = \IPS\downloads\File::load( $id );
if ( $this->member and !$file->can( 'read', $this->member ) )
{
throw new \OutOfRangeException;
}
if ( $this->member and !$file->canEdit( $this->member ) )
{
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2S303/O', 403 );
}
/* New calendar */
if ( isset( \IPS\Request::i()->category ) and \IPS\Request::i()->category != $file->category_id )
/* New category */
if ( isset( \IPS\Request::i()->category ) and \IPS\Request::i()->category != $file->category_id and ( !$this->member or $file->canMove( $this->member ) ) )
{
try
{
$newCategory = \IPS\downloads\Category::load( \IPS\Request::i()->category );
if ( $this->member and !$newCategory->can( 'add', $this->member ) )
{
throw new \OutOfRangeException;
}
$file->move( $newCategory );
}
catch ( \OutOfRangeException $e )
@@ -248,7 +353,7 @@ class _files extends \IPS\Content\Api\ItemController
}
/* New author */
if ( isset( \IPS\Request::i()->author ) )
if ( !$this->member and isset( \IPS\Request::i()->author ) )
{
try
{
@@ -267,11 +372,11 @@ class _files extends \IPS\Content\Api\ItemController
}
/* Everything else */
$this->_createOrUpdate( $file );
$this->_createOrUpdate( $file, 'edit' );
/* Save and return */
$file->save();
return new \IPS\Api\Response( 200, $file->apiOutput() );
return new \IPS\Api\Response( 200, $file->apiOutput( $this->member ) );
}
catch ( \OutOfRangeException $e )
{
@@ -283,14 +388,20 @@ class _files extends \IPS\Content\Api\ItemController
* Create or update file
*
* @param \IPS\Content\Item $item The item
* @param string $type add or edit
* @return \IPS\Content\Item
*/
protected function _createOrUpdate( \IPS\Content\Item $item )
protected function _createOrUpdate( \IPS\Content\Item $item, $type='add' )
{
/* Description */
if ( isset( \IPS\Request::i()->description ) )
{
$item->desc = \IPS\Request::i()->description;
$descriptionContents = \IPS\Request::i()->description;
if ( $this->member )
{
$descriptionContents = \IPS\Text\Parser::parseStatic( $descriptionContents, TRUE, NULL, $this->member, 'downloads_Downloads' );
}
$item->desc = $descriptionContents;
}
/* Version */
@@ -305,8 +416,14 @@ class _files extends \IPS\Content\Api\ItemController
$item->changelog = \IPS\Request::i()->changelog;
}
/* Pass up */
return parent::_createOrUpdate( $item );
$file = parent::_createOrUpdate( $item, $type );
if ( \IPS\Application::appIsEnabled('forums') and $file->container()->forum_id and !$file->hidden() )
{
$file->syncTopic();
}
return $file;
}
/**
@@ -339,7 +456,8 @@ class _files extends \IPS\Content\Api\ItemController
* @apiparam int hidden If 1, only comments which are hidden are returned, if 0 only not hidden
* @apiparam string sortDir Sort direction. Can be 'asc' or 'desc' - defaults to 'asc'
* @apiparam int page Page number
* @throws 2S303/2 INVALID_ID The file ID does not exist
* @apiparam int perPage Number of results per page - defaults to 25
* @throws 2S303/2 INVALID_ID The file ID does not exist or the authorized user does not have permission to view it
* @return \IPS\Api\PaginatedResponse<IPS\downloads\File\Comment>
*/
public function GETitem_comments( $id )
@@ -362,7 +480,8 @@ class _files extends \IPS\Content\Api\ItemController
* @apiparam int hidden If 1, only comments which are hidden are returned, if 0 only not hidden
* @apiparam string sortDir Sort direction. Can be 'asc' or 'desc' - defaults to 'asc'
* @apiparam int page Page number
* @throws 2S303/3 INVALID_ID The file ID does not exist
* @apiparam int perPage Number of results per page - defaults to 25
* @throws 2S303/3 INVALID_ID The file ID does not exist or the authorized user does not have permission to view it
* @return \IPS\Api\PaginatedResponse<IPS\downloads\File\Review>
*/
public function GETitem_reviews( $id )
@@ -382,7 +501,7 @@ class _files extends \IPS\Content\Api\ItemController
* Get previous versions for a file
*
* @param int $id ID Number
* @throws 2S303/4 INVALID_ID The file ID does not exist
* @throws 2S303/4 INVALID_ID The file ID does not exist or the authorized user does not have permission to view it
* @return array
* @apiresponse int id The version ID number (use to get more information about this version in GET /downloads/files/{id})
* @apiresponse string version The version number provided by the user
@@ -394,10 +513,16 @@ class _files extends \IPS\Content\Api\ItemController
try
{
$file = \IPS\downloads\File::load( $id );
if ( $this->member and !$file->can( 'read', $this->member ) )
{
throw new \OutOfRangeException;
}
$versions = array();
foreach ( \IPS\Db::i()->select( '*', 'downloads_filebackup', array( 'b_fileid=?', $id ), 'b_backup DESC' ) as $backup )
{
$versions = array(
$versions[] = array(
'id' => $backup['b_id'],
'version' => $backup['b_version'],
'changelog' => $backup['b_changelog'],
@@ -418,14 +543,21 @@ class _files extends \IPS\Content\Api\ItemController
* Upload a new file version
*
* @apiparam string title The file name
* @apiparam string description The description as HTML (e.g. "<p>This is an file.</p>")
* @apiparam string description The description as HTML (e.g. "<p>This is an file.</p>"). Will be sanatized for requests using an OAuth Access Token for a particular member; will be saved unaltered for requests made using an API Key or the Client Credentials Grant Type.
* @apiparam string version The version number
* @apiparam string changelog What changed in this version
* @apiparam int save If 1 this will be saved as a new version and the previous version available in the history. If 0, will simply replace the existing files/screenshots. Defaults to 1 if versioning is enabled in the category.
* @apiparam int save If 1 this will be saved as a new version and the previous version available in the history. If 0, will simply replace the existing files/screenshots. Defaults to 1. Ignored if category does not have versioning enabled or authorized user does not have permission to disable.
* @reqapiparam object files Files. Keys should be filename (e.g. 'file.txt') and values should be file content - will replace all current files
* @apiparam object screenshots Screenshots. Keys should be filename (e.g. 'screenshot1.png') and values should be file content - will replace all current screenshots
* @throws 2S303/F INVALID_ID The file ID is invalid
* @throws 2S303/F INVALID_ID The file ID is invalid or the authorized user does not have permission to view it
* @throws 1S303/G NO_FILES No files were supplied
* @throws 2S303/Q NO_PERMISSION The authorized user does not have permission to edit the file
* @throws 1S303/I BAD_FILE_EXT One of the files has a file type that is not allowed
* @throws 1S303/J BAD_FILE_SIZE One of the files is too big
* @throws 1S303/K BAD_SS One of the screenshots is not a valid image
* @throws 1S303/L BAD_SS_SIZE One of the screenshots is too big by filesize
* @throws 1S303/M BAD_SS_DIMS One of the screenshots is too big by dimensions
* @throws 1S303/N NO_SS No screenshots are provided, but screenshots are required for the category
* @return \IPS\downloads\File
*/
public function POSTitem_history( $id )
@@ -434,6 +566,14 @@ class _files extends \IPS\Content\Api\ItemController
{
/* Load file */
$file = \IPS\downloads\File::load( $id );
if ( $this->member and !$file->can( 'read', $this->member ) )
{
throw new \OutOfRangeException;
}
if ( $this->member and !$file->canEdit( $this->member ) )
{
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2S303/O', 403 );
}
$category = $file->container();
/* Validate files */
@@ -441,15 +581,23 @@ class _files extends \IPS\Content\Api\ItemController
{
throw new \IPS\Api\Exception( 'NO_FILES', '1L296/B', 400 );
}
if ( $this->member )
{
$this->_validateFilesForMember( $category );
}
/* Save current version? */
if ( !isset( \IPS\Request::i()->save ) )
$save = FALSE;
if ( $category->versioning !== 0 )
{
$save = (bool) $category->versioning !== 0;
}
else
{
$save = (bool) \IPS\Request::i()->save;
if ( $this->member and !$this->member->group['idm_bypass_revision'] )
{
$save = TRUE;
}
else
{
$save = isset( \IPS\Request::i()->save ) ? ( (bool) \IPS\Request::i()->save ) : TRUE;
}
}
if ( $save )
{
@@ -472,9 +620,9 @@ class _files extends \IPS\Content\Api\ItemController
}
/* Insert the new records */
foreach ( \IPS\Request::i()->files as $name => $content )
foreach ( array_keys( \IPS\Request::i()->files ) as $name )
{
$fileObject = \IPS\File::create( 'downloads_Files', $name, $content );
$fileObject = \IPS\File::create( 'downloads_Files', $name, $_POST['files'][ $name ] );
\IPS\Db::i()->insert( 'downloads_files_records', array(
'record_file_id' => $file->id,
@@ -488,9 +636,9 @@ class _files extends \IPS\Content\Api\ItemController
if ( isset( \IPS\Request::i()->screenshots ) )
{
$primary = 1;
foreach ( \IPS\Request::i()->screenshots as $name => $content )
foreach ( array_keys( \IPS\Request::i()->screenshots ) as $name )
{
$fileObject = \IPS\File::create( 'downloads_Screenshots', $name, $content );
$fileObject = \IPS\File::create( 'downloads_Screenshots', $name, $_POST['screenshots'][ $name ] );
\IPS\Db::i()->insert( 'downloads_files_records', array(
'record_file_id' => $file->id,
@@ -509,7 +657,7 @@ class _files extends \IPS\Content\Api\ItemController
}
/* Update */
$file = $this->_createOrUpdate( $file );
$file = $this->_createOrUpdate( $file, 'edit' );
$file = $this->_recalculate( $file );
/* Save */
@@ -523,7 +671,7 @@ class _files extends \IPS\Content\Api\ItemController
}
/* Return */
return new \IPS\Api\Response( 200, $file->apiOutput() );
return new \IPS\Api\Response( 200, $file->apiOutput( $this->member ) );
}
catch ( \OutOfRangeException $e )
{
@@ -537,19 +685,24 @@ class _files extends \IPS\Content\Api\ItemController
*
* @param int $id ID Number
* @throws 2S303/5 INVALID_ID The file ID does not exist
* @throws 2S303/P NO_PERMISSION The authorized user does not have permission to delete the file
* @return void
*/
public function DELETEitem( $id )
{
try
{
\IPS\downloads\File::load( $id )->delete();
$item = \IPS\downloads\File::load( $id );
if ( $this->member and !$item->canDelete( $this->member ) )
{
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2G316/G', 404 );
}
return new \IPS\Api\Response( 200, NULL );
$item->delete();
}
catch ( \OutOfRangeException $e )
{
throw new \IPS\Api\Exception( 'INVALID_ID', '2S303/5', 404 );
throw new \IPS\Api\Exception( 'INVALID_ID', '2S303/P', 404 );
}
}
}