2022-06-17 17:51:59 +02:00
< ? php
namespace Kirby\Cms ;
use Kirby\Filesystem\F ;
use Kirby\Filesystem\IsFile ;
use Kirby\Panel\File as Panel ;
use Kirby\Toolkit\A ;
use Kirby\Toolkit\Str ;
/**
* The `$file` object provides a set
* of methods that can be used when
* dealing with a single image or
* other media file , like getting the
* URL or resizing an image . It also
* handles file meta data .
*
* The File class proxies the `Kirby\Filesystem\File`
* or `Kirby\Image\Image` class , which
* is used to handle all asset file methods .
* In addition the File class handles
* meta data via `Kirby\Cms\Content` .
*
* @ package Kirby Cms
* @ author Bastian Allgeier < bastian @ getkirby . com >
* @ link https :// getkirby . com
* @ copyright Bastian Allgeier
* @ license https :// getkirby . com / license
*/
class File extends ModelWithContent
{
use FileActions ;
use FileModifications ;
use HasMethods ;
use HasSiblings ;
use IsFile ;
public const CLASS_ALIAS = 'file' ;
/**
* Cache for the initialized blueprint object
*
* @ var \Kirby\Cms\FileBlueprint
*/
protected $blueprint ;
/**
* @ var string
*/
protected $filename ;
/**
* @ var string
*/
protected $id ;
/**
* All registered file methods
*
* @ var array
*/
public static $methods = [];
/**
* The parent object
*
* @ var \Kirby\Cms\Model
*/
protected $parent ;
/**
* The absolute path to the file
*
* @ var string | null
*/
protected $root ;
/**
* @ var string
*/
protected $template ;
/**
* The public file Url
*
* @ var string
*/
protected $url ;
/**
* Magic caller for file methods
* and content fields . ( in this order )
*
* @ param string $method
* @ param array $arguments
* @ return mixed
*/
public function __call ( string $method , array $arguments = [])
{
// public property access
if ( isset ( $this -> $method ) === true ) {
return $this -> $method ;
}
// asset method proxy
if ( method_exists ( $this -> asset (), $method )) {
return $this -> asset () -> $method ( ... $arguments );
}
// file methods
if ( $this -> hasMethod ( $method )) {
return $this -> callMethod ( $method , $arguments );
}
// content fields
return $this -> content () -> get ( $method );
}
/**
* Creates a new File object
*
* @ param array $props
*/
public function __construct ( array $props )
{
// set filename as the most important prop first
// TODO: refactor later to avoid redundant prop setting
$this -> setProperty ( 'filename' , $props [ 'filename' ] ? ? null , true );
// set other properties
$this -> setProperties ( $props );
}
/**
* Improved `var_dump` output
*
* @ return array
*/
public function __debugInfo () : array
{
return array_merge ( $this -> toArray (), [
'content' => $this -> content (),
'siblings' => $this -> siblings (),
]);
}
/**
* Returns the url to api endpoint
*
* @ internal
* @ param bool $relative
* @ return string
*/
public function apiUrl ( bool $relative = false ) : string
{
return $this -> parent () -> apiUrl ( $relative ) . '/files/' . $this -> filename ();
}
/**
* Returns the FileBlueprint object for the file
*
* @ return \Kirby\Cms\FileBlueprint
*/
public function blueprint ()
{
if ( is_a ( $this -> blueprint , 'Kirby\Cms\FileBlueprint' ) === true ) {
return $this -> blueprint ;
}
return $this -> blueprint = FileBlueprint :: factory ( 'files/' . $this -> template (), 'files/default' , $this );
}
/**
* Store the template in addition to the
* other content .
*
* @ internal
* @ param array $data
* @ param string | null $languageCode
* @ return array
*/
public function contentFileData ( array $data , string $languageCode = null ) : array
{
return A :: append ( $data , [
'template' => $this -> template (),
]);
}
/**
* Returns the directory in which
* the content file is located
*
* @ internal
* @ return string
*/
public function contentFileDirectory () : string
{
return dirname ( $this -> root ());
}
/**
* Filename for the content file
*
* @ internal
* @ return string
*/
public function contentFileName () : string
{
return $this -> filename ();
}
/**
* Constructs a File object
*
* @ internal
* @ param mixed $props
* @ return static
*/
public static function factory ( $props )
{
return new static ( $props );
}
/**
* Returns the filename with extension
*
* @ return string
*/
public function filename () : string
{
return $this -> filename ;
}
/**
* Returns the parent Files collection
*
* @ return \Kirby\Cms\Files
*/
public function files ()
{
return $this -> siblingsCollection ();
}
/**
* Converts the file to html
*
* @ param array $attr
* @ return string
*/
public function html ( array $attr = []) : string
{
return $this -> asset () -> html ( array_merge (
[ 'alt' => $this -> alt ()],
$attr
));
}
/**
* Returns the id
*
* @ return string
*/
public function id () : string
{
if ( $this -> id !== null ) {
return $this -> id ;
}
if ( is_a ( $this -> parent (), 'Kirby\Cms\Page' ) === true ) {
return $this -> id = $this -> parent () -> id () . '/' . $this -> filename ();
} elseif ( is_a ( $this -> parent (), 'Kirby\Cms\User' ) === true ) {
return $this -> id = $this -> parent () -> id () . '/' . $this -> filename ();
}
return $this -> id = $this -> filename ();
}
/**
* Compares the current object with the given file object
*
* @ param \Kirby\Cms\File $file
* @ return bool
*/
public function is ( File $file ) : bool
{
return $this -> id () === $file -> id ();
}
/**
* Check if the file can be read by the current user
*
* @ return bool
*/
public function isReadable () : bool
{
static $readable = [];
$template = $this -> template ();
if ( isset ( $readable [ $template ]) === true ) {
return $readable [ $template ];
}
return $readable [ $template ] = $this -> permissions () -> can ( 'read' );
}
/**
* Creates a unique media hash
*
* @ internal
* @ return string
*/
public function mediaHash () : string
{
return $this -> mediaToken () . '-' . $this -> modifiedFile ();
}
/**
* Returns the absolute path to the file in the public media folder
*
* @ internal
* @ return string
*/
public function mediaRoot () : string
{
return $this -> parent () -> mediaRoot () . '/' . $this -> mediaHash () . '/' . $this -> filename ();
}
/**
* Creates a non - guessable token string for this file
*
* @ internal
* @ return string
*/
public function mediaToken () : string
{
$token = $this -> kirby () -> contentToken ( $this , $this -> id ());
return substr ( $token , 0 , 10 );
}
/**
* Returns the absolute Url to the file in the public media folder
*
* @ internal
* @ return string
*/
public function mediaUrl () : string
{
return $this -> parent () -> mediaUrl () . '/' . $this -> mediaHash () . '/' . $this -> filename ();
}
/**
* Get the file ' s last modification time .
*
2022-06-17 18:02:55 +02:00
* @ param string | \IntlDateFormatter | null $format
* @ param string | null $handler date , intl or strftime
2022-06-17 17:51:59 +02:00
* @ param string | null $languageCode
* @ return mixed
*/
2022-06-17 18:02:55 +02:00
public function modified ( $format = null , string $handler = null , string $languageCode = null )
2022-06-17 17:51:59 +02:00
{
$file = $this -> modifiedFile ();
$content = $this -> modifiedContent ( $languageCode );
$modified = max ( $file , $content );
$handler ? ? = $this -> kirby () -> option ( 'date.handler' , 'date' );
return Str :: date ( $modified , $format , $handler );
}
/**
* Timestamp of the last modification
* of the content file
*
* @ param string | null $languageCode
* @ return int
*/
protected function modifiedContent ( string $languageCode = null ) : int
{
return F :: modified ( $this -> contentFile ( $languageCode ));
}
/**
* Timestamp of the last modification
* of the source file
*
* @ return int
*/
protected function modifiedFile () : int
{
return F :: modified ( $this -> root ());
}
/**
* Returns the parent Page object
*
* @ return \Kirby\Cms\Page | null
*/
public function page ()
{
return is_a ( $this -> parent (), 'Kirby\Cms\Page' ) === true ? $this -> parent () : null ;
}
/**
* Returns the panel info object
*
* @ return \Kirby\Panel\File
*/
public function panel ()
{
return new Panel ( $this );
}
/**
* Returns the parent Model object
*
* @ return \Kirby\Cms\Model
*/
public function parent ()
{
return $this -> parent ? ? = $this -> kirby () -> site ();
}
/**
* Returns the parent id if a parent exists
*
* @ internal
* @ todo 3.7 . 0 When setParent () is changed , the if check is not needed anymore
* @ return string | null
*/
public function parentId () : ? string
{
if ( $parent = $this -> parent ()) {
return $parent -> id ();
}
return null ;
}
/**
* Returns a collection of all parent pages
*
* @ return \Kirby\Cms\Pages
*/
public function parents ()
{
if ( is_a ( $this -> parent (), 'Kirby\Cms\Page' ) === true ) {
return $this -> parent () -> parents () -> prepend ( $this -> parent () -> id (), $this -> parent ());
}
return new Pages ();
}
/**
* Returns the permissions object for this file
*
* @ return \Kirby\Cms\FilePermissions
*/
public function permissions ()
{
return new FilePermissions ( $this );
}
/**
* Returns the absolute root to the file
*
* @ return string | null
*/
public function root () : ? string
{
return $this -> root ? ? = $this -> parent () -> root () . '/' . $this -> filename ();
}
/**
* Returns the FileRules class to
* validate any important action .
*
* @ return \Kirby\Cms\FileRules
*/
protected function rules ()
{
return new FileRules ();
}
/**
* Sets the Blueprint object
*
* @ param array | null $blueprint
* @ return $this
*/
protected function setBlueprint ( array $blueprint = null )
{
if ( $blueprint !== null ) {
$blueprint [ 'model' ] = $this ;
$this -> blueprint = new FileBlueprint ( $blueprint );
}
return $this ;
}
/**
* Sets the filename
*
* @ param string $filename
* @ return $this
*/
protected function setFilename ( string $filename )
{
$this -> filename = $filename ;
return $this ;
}
/**
* Sets the parent model object ;
* this property is required for `File::create()` and
* will be generally required starting with Kirby 3.7 . 0
*
* @ param \Kirby\Cms\Model | null $parent
* @ return $this
* @ todo make property required in 3.7 . 0
*/
protected function setParent ( Model $parent = null )
{
// @codeCoverageIgnoreStart
if ( $parent === null ) {
deprecated ( 'You are creating a `Kirby\Cms\File` object without passing the `parent` property. While unsupported, this hasn\'t caused any direct errors so far. To fix inconsistencies, the `parent` property will be required when creating a `Kirby\Cms\File` object in Kirby 3.7.0 and higher. Not passing this property will start throwing a breaking error.' );
}
// @codeCoverageIgnoreEnd
$this -> parent = $parent ;
return $this ;
}
/**
* Always set the root to null , to invoke
* auto root detection
*
* @ param string | null $root
* @ return $this
*/
protected function setRoot ( string $root = null )
{
$this -> root = null ;
return $this ;
}
/**
* @ param string | null $template
* @ return $this
*/
protected function setTemplate ( string $template = null )
{
$this -> template = $template ;
return $this ;
}
/**
* Sets the url
*
* @ param string | null $url
* @ return $this
*/
protected function setUrl ( string $url = null )
{
$this -> url = $url ;
return $this ;
}
/**
* Returns the parent Files collection
* @ internal
*
* @ return \Kirby\Cms\Files
*/
protected function siblingsCollection ()
{
return $this -> parent () -> files ();
}
/**
* Returns the parent Site object
*
* @ return \Kirby\Cms\Site
*/
public function site ()
{
return is_a ( $this -> parent (), 'Kirby\Cms\Site' ) === true ? $this -> parent () : $this -> kirby () -> site ();
}
/**
* Returns the final template
*
* @ return string | null
*/
public function template () : ? string
{
return $this -> template ? ? = $this -> content () -> get ( 'template' ) -> value ();
}
/**
* Returns siblings with the same template
*
* @ param bool $self
* @ return \Kirby\Cms\Files
*/
public function templateSiblings ( bool $self = true )
{
return $this -> siblings ( $self ) -> filter ( 'template' , $this -> template ());
}
/**
* Extended info for the array export
* by injecting the information from
* the asset .
*
* @ return array
*/
public function toArray () : array
{
return array_merge ( $this -> asset () -> toArray (), parent :: toArray ());
}
/**
* Returns the Url
*
* @ return string
*/
public function url () : string
{
return $this -> url ? ? = ( $this -> kirby () -> component ( 'file::url' ))( $this -> kirby (), $this );
}
/**
* Deprecated !
*/
/**
* Provides a kirbytag or markdown
* tag for the file , which will be
* used in the panel , when the file
* gets dragged onto a textarea
*
* @ todo Add `deprecated()` helper warning in 3.7 . 0
* @ todo Remove in 3.8 . 0
*
* @ internal
* @ param string | null $type ( null | auto | kirbytext | markdown )
* @ param bool $absolute
* @ return string
* @ codeCoverageIgnore
*/
public function dragText ( string $type = null , bool $absolute = false ) : string
{
return $this -> panel () -> dragText ( $type , $absolute );
}
/**
* Returns an array of all actions
* that can be performed in the Panel
*
* @ todo Add `deprecated()` helper warning in 3.7 . 0
* @ todo Remove in 3.8 . 0
*
* @ since 3.3 . 0 This also checks for the lock status
* @ since 3.5 . 1 This also checks for matching accept settings
*
* @ param array $unlock An array of options that will be force - unlocked
* @ return array
* @ codeCoverageIgnore
*/
public function panelOptions ( array $unlock = []) : array
{
return $this -> panel () -> options ( $unlock );
}
/**
* Returns the full path without leading slash
*
* @ todo Add `deprecated()` helper warning in 3.7 . 0
* @ todo Remove in 3.8 . 0
*
* @ internal
* @ return string
* @ codeCoverageIgnore
*/
public function panelPath () : string
{
return $this -> panel () -> path ();
}
/**
* Prepares the response data for file pickers
* and file fields
*
* @ todo Add `deprecated()` helper warning in 3.7 . 0
* @ todo Remove in 3.8 . 0
*
* @ param array | null $params
* @ return array
* @ codeCoverageIgnore
*/
public function panelPickerData ( array $params = []) : array
{
return $this -> panel () -> pickerData ( $params );
}
/**
* Returns the url to the editing view
* in the panel
*
* @ todo Add `deprecated()` helper warning in 3.7 . 0
* @ todo Remove in 3.8 . 0
*
* @ internal
* @ param bool $relative
* @ return string
* @ codeCoverageIgnore
*/
public function panelUrl ( bool $relative = false ) : string
{
return $this -> panel () -> url ( $relative );
}
/**
* Simplified File URL that uses the parent
* Page URL and the filename as a more stable
* alternative for the media URLs .
*
* @ return string
*/
public function previewUrl () : string
{
$parent = $this -> parent ();
$url = url ( $this -> id ());
switch ( $parent :: CLASS_ALIAS ) {
case 'page' :
$preview = $parent -> blueprint () -> preview ();
// the page has a custom preview setting,
// thus the file is only accessible through
// the direct media URL
if ( $preview !== true ) {
return $this -> url ();
}
// it's more stable to access files for drafts
// through their direct URL to avoid conflicts
// with draft token verification
if ( $parent -> isDraft () === true ) {
return $this -> url ();
}
return $url ;
case 'user' :
return $this -> url ();
default :
return $url ;
}
}
}