2022-03-08 15:55:41 +01:00
|
|
|
<?php namespace ProcessWire;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ProcessWire Pagefiles
|
|
|
|
*
|
|
|
|
* #pw-summary Pagefiles is a type of WireArray that contains Pagefile objects. It also acts as the value for multi-file fields in ProcessWire.
|
|
|
|
* #pw-body =
|
|
|
|
* The items in a Pagefiles array are `Pagefile` objects, indexed by file basename, i.e. `myfile.pdf`.
|
|
|
|
* Information on most traversal, filtering and manipulation methods can be found in the `WireArray` class that Pagefiles extends.
|
|
|
|
* In the examples below, `$page->files` is an instance of Pagefiles:
|
|
|
|
* ~~~~~
|
|
|
|
* // Determining if any files are present
|
|
|
|
* if($page->files->count()) {
|
|
|
|
* // There are files here
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* // Traversing and outputting links to all files
|
|
|
|
* foreach($page->files as $name => $pagefile) {
|
|
|
|
* echo "<li><a href='$pagefile->url'>$name: $pagefile->description</a></li>";
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* // Adding new file(s)
|
|
|
|
* $page->files->add('/path/to/file.pdf');
|
|
|
|
* $page->files->add('http://domain.com/photo.png');
|
|
|
|
* $page->save('files');
|
|
|
|
*
|
|
|
|
* // Getting file by name
|
|
|
|
* $pagefile = $page->files->getFile('file.pdf');
|
|
|
|
* $pagefile = $page->files['file.pdf']; // alternate
|
|
|
|
*
|
|
|
|
* // Getting first and last file
|
|
|
|
* $pagefile = $page->files->first();
|
|
|
|
* $pagefile = $page->files->last();
|
|
|
|
* ~~~~~
|
|
|
|
*
|
|
|
|
* #pw-body
|
|
|
|
*
|
|
|
|
* Typically a Pagefiles object will be associated with a specific field attached to a Page.
|
|
|
|
* There may be multiple instances of Pagefiles attached to a given Page (depending on what fields are in it's fieldgroup).
|
|
|
|
*
|
|
|
|
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
|
|
|
|
* https://processwire.com
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @property string $path Returns the full server disk path where files are stored.
|
|
|
|
* @property string $url Returns the URL where files are stored.
|
|
|
|
* @property Page $page Returns the Page that contains this set of files, same as the getPage() method. #pw-group-other
|
|
|
|
* @property Field $field Returns the Field that contains this set of files, same as the getField() method. #pw-group-other
|
|
|
|
* @method Pagefiles delete() delete(Pagefile $file) Removes the file and deletes from disk when page is saved. #pw-group-manipulation
|
|
|
|
* @method Pagefile|bool clone(Pagefile $item, array $options = array()) Duplicate a file and return it. #pw-group-manipulation
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
class Pagefiles extends WireArray implements PageFieldValueInterface {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The Page object associated with these Pagefiles
|
|
|
|
*
|
|
|
|
* @var Page
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $page;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The Field object associated with these Pagefiles
|
|
|
|
*
|
|
|
|
* @var Field
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $field;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Items to be deleted when Page is saved
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $unlinkQueue = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Items to be renamed when Page is saved (oldName => newName)
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $renameQueue = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Items to be made non-temp upon page save (like duplicated files)
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $unTempQueue = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* IDs of any hooks added in this instance, used by the destructor
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $hookIDs = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether or not this is a formatted value
|
|
|
|
*
|
|
|
|
* @var bool
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $formatted = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Template|null
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected $fieldsTemplate = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct a Pagefiles object
|
|
|
|
*
|
|
|
|
* @param Page $page The page associated with this Pagefiles instance
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function __construct(Page $page) {
|
|
|
|
$this->setPage($page);
|
|
|
|
parent::__construct();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destruct and ensure that hooks are removed
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function __destruct() {
|
|
|
|
$this->removeHooks();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove hooks to the PagefilesManager instance
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected function removeHooks() {
|
|
|
|
if(count($this->hookIDs) && $this->page && $this->page->filesManager) {
|
|
|
|
foreach($this->hookIDs as $id) $this->page->filesManager->removeHook($id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the Page these files are assigned to
|
|
|
|
*
|
|
|
|
* @param Page $page
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function setPage(Page $page) {
|
|
|
|
$this->page = $page;
|
|
|
|
// call the filesmanager, just to ensure paths are where they should be
|
|
|
|
$page->filesManager();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the field these files are assigned to
|
|
|
|
*
|
|
|
|
* @param Field $field
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function setField(Field $field) {
|
|
|
|
$this->field = $field;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the page these files are assigned to
|
|
|
|
*
|
|
|
|
* @return Page
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function getPage() {
|
|
|
|
return $this->page;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the field these files are assigned to
|
|
|
|
*
|
|
|
|
* @return Field|null Returns Field, or null if Field has not yet been assigned.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function getField() {
|
|
|
|
return $this->field;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new blank instance of itself. For internal use, part of the WireArray interface.
|
|
|
|
*
|
|
|
|
* Adapted here so that $this->page can be passed to the constructor of a newly created Pagefiles.
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @return Pagefiles|WireArray
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function makeNew() {
|
|
|
|
$class = get_class($this);
|
|
|
|
$newArray = $this->wire(new $class($this->page));
|
|
|
|
$newArray->setField($this->field);
|
|
|
|
return $newArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Make a copy, overriding the default clone method used by WireArray::makeCopy
|
|
|
|
*
|
|
|
|
* This is necessary because our __clone() makes new copies of each Pagefile (deep clone)
|
|
|
|
* and we don't want that to occur for the regular find() and filter() operations that
|
|
|
|
* make use of makeCopy().
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @return Pagefiles
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function makeCopy() {
|
|
|
|
$newArray = $this->makeNew();
|
|
|
|
foreach($this->data as $key => $value) $newArray[$key] = $value;
|
|
|
|
foreach($this->extraData as $key => $value) $newArray->data($key, $value);
|
|
|
|
$newArray->resetTrackChanges($this->trackChanges());
|
|
|
|
foreach($newArray as $item) $item->setPagefilesParent($newArray);
|
|
|
|
return $newArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When Pagefiles is cloned, ensure that the individual Pagefile items are also cloned
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function __clone() {
|
|
|
|
foreach($this as $key => $pagefile) {
|
|
|
|
$pagefile = clone $pagefile;
|
|
|
|
$pagefile->setPagefilesParent($this);
|
|
|
|
$this->set($key, $pagefile);
|
|
|
|
}
|
|
|
|
parent::__clone();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Per the WireArray interface, items must be of type Pagefile
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @param mixed $item
|
|
|
|
* @return bool
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function isValidItem($item) {
|
|
|
|
return $item instanceof Pagefile;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Per the WireArray interface, items are indexed by Pagefile::basename
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @param mixed $item
|
|
|
|
* @return string
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function getItemKey($item) {
|
|
|
|
return $item->basename;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Per the WireArray interface, return a blank Pagefile
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @return Pagefile
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function makeBlankItem() {
|
|
|
|
return $this->wire(new Pagefile($this, ''));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a value from this Pagefiles instance
|
|
|
|
*
|
|
|
|
* You may also specify a file's 'tag' and it will return the first Pagefile matching the tag.
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @param string $key
|
|
|
|
* @return mixed
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function get($key) {
|
|
|
|
if($key == 'page') return $this->getPage();
|
|
|
|
if($key == 'field') return $this->getField();
|
|
|
|
if($key == 'url') return $this->url();
|
|
|
|
if($key == 'path') return $this->path();
|
|
|
|
return parent::get($key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get for direct access to properties
|
|
|
|
*
|
|
|
|
* @param int|string $key
|
|
|
|
* @return bool|mixed|Page|Wire|WireData
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function __get($key) {
|
|
|
|
if(in_array($key, array('page', 'field', 'url', 'path'))) return $this->get($key);
|
|
|
|
return parent::__get($key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find all Pagefiles matching the given selector string or tag
|
|
|
|
*
|
|
|
|
* @param string $selector
|
|
|
|
* @return Pagefiles New instance of Pagefiles
|
|
|
|
*
|
|
|
|
public function find($selector) {
|
|
|
|
if(!Selectors::stringHasOperator($selector)) {
|
|
|
|
// if there is no selector operator in the strong, consider it a tag first
|
|
|
|
$value = $this->findTag($selector);
|
|
|
|
// if it didn't match any tag, then see if it matches in some other way
|
|
|
|
if(!count($value)) $value = parent::find($selector);
|
|
|
|
} else {
|
|
|
|
// there is an operator so we send it straight to WireArray
|
|
|
|
$value = parent::find($selector);
|
|
|
|
}
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a new Pagefile item or filename
|
|
|
|
*
|
|
|
|
* If give a filename (string) it will create the new `Pagefile` item from it and add it.
|
|
|
|
*
|
|
|
|
* #pw-group-manipulation
|
|
|
|
*
|
|
|
|
* @param Pagefile|string $item If item is a string (filename) it will create the new `Pagefile` item from it and add it.
|
|
|
|
* @return $this
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function add($item) {
|
|
|
|
|
|
|
|
if(is_string($item)) {
|
|
|
|
/** @var Pagefile $item */
|
|
|
|
$item = $this->wire(new Pagefile($this, $item));
|
|
|
|
|
|
|
|
} else if($item instanceof Pagefile) {
|
|
|
|
$page = $this->get('page');
|
|
|
|
if($page && "$page" !== "$item->page") {
|
|
|
|
$newItem = clone $item;
|
|
|
|
$newItem->setPagefilesParent($this);
|
|
|
|
$newItem->install($item->filename);
|
|
|
|
$newItem->isTemp(true);
|
|
|
|
$this->unTempQueue($newItem);
|
|
|
|
$this->message("Copied $item->url to $newItem->url", Notice::debug);
|
|
|
|
$item = $newItem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @var Pagefiles $result */
|
|
|
|
$result = parent::add($item);
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Make any removals take effect on disk
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function hookPageSave() {
|
|
|
|
|
|
|
|
if($this->page && $this->field && !$this->page->isChanged($this->field->name)) return $this;
|
|
|
|
|
|
|
|
$this->page->filesManager()->uncache();
|
|
|
|
|
|
|
|
foreach($this->unTempQueue as $item) {
|
|
|
|
$item->isTemp(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach($this->unlinkQueue as $item) {
|
|
|
|
$item->unlink();
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach($this->renameQueue as $item) {
|
|
|
|
$name = $item->get('_rename');
|
|
|
|
if(!$name) continue;
|
|
|
|
$item->rename($name);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->unTempQueue = array();
|
|
|
|
$this->unlinkQueue = array();
|
|
|
|
$this->renameQueue = array();
|
|
|
|
$this->removeHooks();
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function addSaveHook() {
|
|
|
|
if(!count($this->unlinkQueue) && !count($this->renameQueue) && !count($this->unTempQueue)) {
|
|
|
|
$this->hookIDs[] = $this->page->filesManager->addHookBefore('save', $this, 'hookPageSave');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete a pagefile item
|
|
|
|
*
|
|
|
|
* Deletes the filename associated with the Pagefile and removes it from this Pagefiles array.
|
|
|
|
* The actual deletion of the file does not take effect until `$page->save()`.
|
|
|
|
*
|
|
|
|
* #pw-group-manipulation
|
|
|
|
*
|
|
|
|
* @param Pagefile|string $item Pagefile or basename
|
|
|
|
* @return $this
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function ___delete($item) {
|
|
|
|
return $this->remove($item);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete/remove a Pagefile item
|
|
|
|
*
|
|
|
|
* Deletes the filename associated with the Pagefile and removes it from this Pagefiles array.
|
|
|
|
* The actual deletion of the file does not take effect until `$page->save()`.
|
|
|
|
*
|
|
|
|
* #pw-internal Please use the hookable delete() method for public API
|
|
|
|
*
|
|
|
|
* @param Pagefile $item Item to delete/remove.
|
|
|
|
* @return $this
|
|
|
|
* @throws WireException
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function remove($item) {
|
|
|
|
if(is_string($item)) $item = $this->get($item);
|
|
|
|
if(!$this->isValidItem($item)) throw new WireException("Invalid type to {$this->className}::remove(item)");
|
|
|
|
$this->addSaveHook();
|
|
|
|
$this->unlinkQueue[] = $item;
|
|
|
|
parent::remove($item);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete all files associated with this Pagefiles instance, leaving a blank Pagefiles instance.
|
|
|
|
*
|
|
|
|
* The actual deletion of the files does not take effect until `$page->save()`.
|
|
|
|
*
|
|
|
|
* #pw-group-manipulation
|
|
|
|
*
|
|
|
|
* @return $this
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function deleteAll() {
|
|
|
|
foreach($this as $item) {
|
|
|
|
$this->delete($item);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Queue a rename of a Pagefile
|
|
|
|
*
|
|
|
|
* This only queues a rename. Rename actually occurs when page is saved.
|
|
|
|
* Note this differs from the behavior of `Pagefile::rename()`.
|
|
|
|
*
|
|
|
|
* #pw-group-manipulation
|
|
|
|
*
|
|
|
|
* @param Pagefile $item
|
|
|
|
* @param string $name
|
|
|
|
* @return Pagefiles
|
|
|
|
* @see Pagefile::rename()
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function rename(Pagefile $item, $name) {
|
|
|
|
$item->set('_rename', $name);
|
|
|
|
$this->renameQueue[] = $item;
|
|
|
|
$this->trackChange('renameQueue', $item->name, $name);
|
|
|
|
$this->addSaveHook();
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Duplicate the Pagefile and add to this Pagefiles instance
|
|
|
|
*
|
|
|
|
* After duplicating a file, you must follow up with a save of the page containing it.
|
|
|
|
* Otherwise the file is marked for deletion.
|
|
|
|
*
|
|
|
|
* @param Pagefile $item Pagefile item to duplicate
|
|
|
|
* @param array $options Options to modify default behavior:
|
|
|
|
* - `action` (string): Specify "append", "prepend", "after", "before" or blank to only return Pagefile. (default="after")
|
|
|
|
* - `pagefiles` (Pagefiles): Pagefiles instance file should be duplicated to. (default=$this)
|
|
|
|
* @return Pagefile|bool Returns new Pagefile or boolean false on fail
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function ___clone(Pagefile $item, array $options = array()) {
|
|
|
|
|
|
|
|
$defaults = array(
|
|
|
|
'action' => 'after',
|
|
|
|
'pagefiles' => $this,
|
|
|
|
);
|
|
|
|
|
|
|
|
$options = array_merge($defaults, $options);
|
|
|
|
/** @var Pagefiles $pagefiles */
|
|
|
|
$pagefiles = $options['pagefiles'];
|
|
|
|
$itemCopy = false;
|
|
|
|
$path = $pagefiles->path();
|
|
|
|
$parts = explode('.', $item->basename(), 2);
|
|
|
|
$n = $path === $this->path() ? 1 : 0;
|
|
|
|
|
|
|
|
if($n && preg_match('/^(.+?)-(\d+)$/', $parts[0], $matches)) {
|
|
|
|
$parts[0] = $matches[1];
|
|
|
|
$n = (int) $matches[2];
|
|
|
|
if(!$n) $n = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
$pathname = $n ? ($path . $parts[0] . "-$n." . $parts[1]) : ($path . $item->basename);
|
|
|
|
} while(file_exists($pathname) && $n++);
|
|
|
|
|
|
|
|
if(copy($item->filename(), $pathname)) {
|
|
|
|
$this->wire('files')->chmod($pathname);
|
|
|
|
|
|
|
|
$itemCopy = clone $item;
|
|
|
|
$itemCopy->setPagefilesParent($pagefiles);
|
|
|
|
$itemCopy->setFilename($pathname);
|
|
|
|
$itemCopy->isTemp(true);
|
|
|
|
|
|
|
|
switch($options['action']) {
|
|
|
|
case 'append': $pagefiles->append($itemCopy); break;
|
|
|
|
case 'prepend': $pagefiles->prepend($itemCopy); break;
|
|
|
|
case 'before': $pagefiles->insertBefore($itemCopy, $item); break;
|
|
|
|
case 'after': $pagefiles->insertAfter($itemCopy, $item); break;
|
|
|
|
}
|
|
|
|
|
|
|
|
$pagefiles->unTempQueue($itemCopy);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $itemCopy;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the full disk path where files are stored
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function path() {
|
|
|
|
return $this->page->filesManager->path();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the web accessible index URL where files are stored
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function url() {
|
|
|
|
return $this->page->filesManager->url();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a basename, this method returns a clean version containing valid characters
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @param string $basename May also be a full path/filename, but it will still return a basename
|
|
|
|
* @param bool $originalize If true, it will generate an original filename if $basename already exists
|
|
|
|
* @param bool $allowDots If true, dots "." are allowed in the basename portion of the filename.
|
|
|
|
* @param bool $translate True if we should translate accented characters to ascii equivalents (rather than substituting underscores)
|
|
|
|
* @return string
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function cleanBasename($basename, $originalize = false, $allowDots = true, $translate = false) {
|
|
|
|
|
2022-11-05 18:32:48 +01:00
|
|
|
$sanitizer = $this->wire()->sanitizer;
|
2022-03-08 15:55:41 +01:00
|
|
|
$basename = function_exists('mb_strtolower') ? mb_strtolower($basename) : strtolower($basename);
|
|
|
|
$dot = strrpos($basename, '.');
|
2022-11-05 18:32:48 +01:00
|
|
|
$ext = $dot ? substr($basename, $dot) : '';
|
2022-03-08 15:55:41 +01:00
|
|
|
$basename = basename($basename, $ext);
|
2022-11-05 18:32:48 +01:00
|
|
|
while(strpos($basename, '..') !== false) $basename = str_replace('..', '', $basename);
|
2022-03-08 15:55:41 +01:00
|
|
|
$test = str_replace(array('-', '_', '.'), '', $basename);
|
|
|
|
|
|
|
|
if(!ctype_alnum($test)) {
|
|
|
|
if($translate) {
|
2022-11-05 18:32:48 +01:00
|
|
|
$basename = $sanitizer->filename($basename, Sanitizer::translate);
|
2022-03-08 15:55:41 +01:00
|
|
|
} else {
|
|
|
|
$basename = preg_replace('/[^-_.a-z0-9]/', '_', $basename);
|
2022-11-05 18:32:48 +01:00
|
|
|
$basename = $sanitizer->filename($basename);
|
2022-03-08 15:55:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!ctype_alnum(ltrim($ext, '.'))) $ext = preg_replace('/[^a-z0-9.]/', '_', $ext);
|
|
|
|
if(!$allowDots && strpos($basename, '.') !== false) $basename = str_replace('.', '_', $basename);
|
|
|
|
$basename .= $ext;
|
|
|
|
|
|
|
|
if($originalize) {
|
|
|
|
$path = $this->path();
|
|
|
|
$n = 0;
|
|
|
|
$p = pathinfo($basename);
|
|
|
|
while(is_file($path . $basename)) {
|
|
|
|
$n++;
|
|
|
|
$basename = "$p[filename]-$n.$p[extension]"; // @hani
|
|
|
|
// $basename = (++$n) . "_" . preg_replace('/^\d+_/', '', $basename);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $basename;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return all Pagefile objects that have the given tag(s).
|
|
|
|
*
|
|
|
|
* Given tag may be any of the following:
|
|
|
|
*
|
|
|
|
* - `foo` (single tag): Will return all Pagefile objects having the specified tag.
|
|
|
|
* - `foo|bar|baz` (multiple OR tags): Will return Pagefile objects having at least one of the tags listed.
|
|
|
|
* - `foo,bar,baz` (multiple AND tags): Will return Pagefile objects having ALL of the tags listed (since 3.0.17).
|
|
|
|
* - `['foo','bar','baz']` (multiple AND tags array): Same as above but can be specified as an array (since 3.0.17).
|
|
|
|
*
|
|
|
|
* #pw-group-tags
|
|
|
|
* #pw-changelog 3.0.17 Added support for multiple AND tags and allow tag specified as an array.
|
|
|
|
*
|
|
|
|
* @param string|array $tag
|
|
|
|
* @return Pagefiles New Pagefiles array with items that matched the given tag(s).
|
|
|
|
* @see Pagefiles::getTag(), Pagefile::hasTag(), Pagefile::tags()
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function findTag($tag) {
|
|
|
|
$items = $this->makeNew();
|
|
|
|
foreach($this as $pagefile) {
|
|
|
|
if($pagefile->hasTag($tag)) $items->add($pagefile);
|
|
|
|
}
|
|
|
|
return $items;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the first Pagefile that matches the given tag or NULL if no match
|
|
|
|
*
|
|
|
|
* Given tag may be any of the following:
|
|
|
|
*
|
|
|
|
* - `foo` (single tag): Will return the first Pagefile object having the specified tag.
|
|
|
|
* - `foo|bar|baz` (multiple OR tags): Will return first Pagefile object having at least one of the tags listed.
|
|
|
|
* - `foo,bar,baz` (multiple AND tags): Will return first Pagefile object having ALL of the tags listed (since 3.0.17).
|
|
|
|
* - `['foo','bar','baz']` (multiple AND tags array): Same as above but can be specified as an array (since 3.0.17).
|
|
|
|
*
|
|
|
|
* #pw-group-tags
|
|
|
|
* #pw-changelog 3.0.17 Added support for multiple AND tags and allow tag specified as an array.
|
|
|
|
*
|
|
|
|
* @param string $tag
|
|
|
|
* @return Pagefile|null
|
|
|
|
* @see Pagefiles::findTag(), Pagefile::hasTag(), Pagefile::tags()
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function getTag($tag) {
|
|
|
|
$item = null;
|
|
|
|
foreach($this as $pagefile) {
|
|
|
|
if(!$pagefile->hasTag($tag)) continue;
|
|
|
|
$item = $pagefile;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return $item;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get list of tags for all files in this Pagefiles array, or return files matching given tag(s)
|
|
|
|
*
|
|
|
|
* This method can either return a list of all tags available, or return all files
|
|
|
|
* matching the given tag or tags (an alias of findTag method).
|
|
|
|
*
|
|
|
|
* ~~~~~
|
|
|
|
* // Get string of all tags
|
|
|
|
* $tagsString = $page->files->tags();
|
|
|
|
*
|
|
|
|
* // Get array of all tags
|
|
|
|
* $tagsArray = $page->files->tags(true);
|
|
|
|
*
|
|
|
|
* // Find all files matching given tag
|
|
|
|
* $pagefiles = $page->files->tags('foobar');
|
|
|
|
* ~~~~~
|
|
|
|
*
|
|
|
|
* #pw-group-tags
|
|
|
|
*
|
|
|
|
* @param bool|string|array $value Specify one of the following:
|
|
|
|
* - Omit to return all tags as a string.
|
|
|
|
* - Boolean true if you want to return tags as an array (rather than string).
|
|
|
|
* - Boolean false to return tags as an array, with lowercase enforced.
|
|
|
|
* - String if you want to return files matching tags (See `Pagefiles::findTag()` method for usage)
|
|
|
|
* - Array if you want to return files matching tags (See `Pagefiles::findTag()` method for usage)
|
|
|
|
* @return string|array|Pagefiles Returns all tags as a string or an array, or Pagefiles matching given tag(s).
|
|
|
|
* When a tags array is returned, it is an associative array where the key and value are both the tag (keys are always lowercase).
|
|
|
|
* @see Pagefiles::findTag(), Pagefile::tags()
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function tags($value = null) {
|
|
|
|
|
|
|
|
if($value === null) {
|
|
|
|
$returnString = true;
|
|
|
|
$value = true;
|
|
|
|
} else {
|
|
|
|
$returnString = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(is_bool($value)) {
|
|
|
|
// return array of tags
|
|
|
|
$tags = array();
|
|
|
|
foreach($this as $pagefile) {
|
|
|
|
$tags = array_merge($tags, $pagefile->tags($value));
|
|
|
|
}
|
|
|
|
if($returnString) $tags = implode(' ', $tags);
|
|
|
|
return $tags;
|
|
|
|
}
|
|
|
|
|
|
|
|
// fallback to behavior of findTag
|
|
|
|
return $this->findTag($value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Track a change
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @param string $what
|
|
|
|
* @param null $old
|
|
|
|
* @param null $new
|
|
|
|
* @return $this
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function trackChange($what, $old = null, $new = null) {
|
|
|
|
if($this->field && $this->page) $this->page->trackChange($this->field->name);
|
|
|
|
/** @var Pagefiles $result */
|
|
|
|
$result = parent::trackChange($what, $old, $new);
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the Pagefile having the given basename, or null if not found.
|
|
|
|
*
|
|
|
|
* @param string $name
|
|
|
|
* @return null|Pagefile
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function getFile($name) {
|
|
|
|
$hasFile = null;
|
|
|
|
$name = basename($name);
|
|
|
|
foreach($this as $pagefile) {
|
|
|
|
if($pagefile->basename == $name) {
|
|
|
|
$hasFile = $pagefile;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $hasFile;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if the given Pagefile is temporary, not yet published.
|
|
|
|
*
|
|
|
|
* You may also provide a 2nd argument boolean to set the temp status or check if temporary AND deletable.
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @param Pagefile $pagefile
|
|
|
|
* @param bool|string $set Optionally set the temp status to true or false, or specify string "deletable" to check if file is temporary AND deletable.
|
|
|
|
* @return bool
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function isTemp(Pagefile $pagefile, $set = null) {
|
|
|
|
|
|
|
|
$isTemp = Pagefile::createdTemp == $pagefile->created;
|
|
|
|
$checkDeletable = ($set === 'deletable' || $set === 'deleteable');
|
|
|
|
|
|
|
|
if(!is_bool($set)) {
|
|
|
|
// temp status is not being set
|
|
|
|
if(!$isTemp) return false; // if not a temp file, we can exit now
|
|
|
|
if(!$checkDeletable) return $isTemp; // if not checking deletable, we can exit now
|
|
|
|
}
|
|
|
|
|
|
|
|
$user = $this->wire('user');
|
|
|
|
$now = time();
|
|
|
|
$session = $this->wire('session');
|
|
|
|
$pageID = $this->page ? $this->page->id : 0;
|
|
|
|
$fieldID = $this->field ? $this->field->id : 0;
|
|
|
|
$sessionKey = "tempFiles_{$pageID}_{$fieldID}";
|
|
|
|
$tempFiles = $pageID && $fieldID ? $session->get($this, $sessionKey) : array();
|
|
|
|
if(!is_array($tempFiles)) $tempFiles = array();
|
|
|
|
|
|
|
|
if($isTemp && $checkDeletable) {
|
|
|
|
$isTemp = false;
|
|
|
|
if(isset($tempFiles[$pagefile->basename])) {
|
|
|
|
// if file was uploaded in this session and still temp, it is deletable
|
|
|
|
$isTemp = true;
|
|
|
|
} else if($pagefile->modified < ($now - 14400)) {
|
|
|
|
// if file was added more than 4 hours ago, it is deletable, regardless who added it
|
|
|
|
$isTemp = true;
|
|
|
|
}
|
|
|
|
// isTemp means isDeletable at this point
|
|
|
|
if($isTemp) {
|
|
|
|
unset($tempFiles[$pagefile->basename]);
|
|
|
|
// remove file from session - note that this means a 'deletable' check can only be used once, for newly uploaded files
|
|
|
|
// as it is assumed you will be removing the file as a result of this method call
|
|
|
|
if(count($tempFiles)) $session->set($this, $sessionKey, $tempFiles);
|
|
|
|
else $session->remove($this, $sessionKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if($set === true) {
|
|
|
|
// set temporary status to true
|
|
|
|
$pagefile->created = Pagefile::createdTemp;
|
|
|
|
$pagefile->modified = $now;
|
|
|
|
$pagefile->createdUser = $user;
|
|
|
|
$pagefile->modifiedUser = $user;
|
|
|
|
// mtime atime
|
|
|
|
@touch($pagefile->filename, Pagefile::createdTemp, $now);
|
|
|
|
$isTemp = true;
|
|
|
|
if($pageID && $fieldID) {
|
|
|
|
$tempFiles[$pagefile->basename] = 1;
|
|
|
|
$session->set($this, $sessionKey, $tempFiles);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if($set === false && $isTemp) {
|
|
|
|
// set temporary status to false
|
|
|
|
$pagefile->created = $now;
|
|
|
|
$pagefile->modified = $now;
|
|
|
|
$pagefile->createdUser = $user;
|
|
|
|
$pagefile->modifiedUser = $user;
|
|
|
|
@touch($pagefile->filename, $now);
|
|
|
|
$isTemp = false;
|
|
|
|
|
|
|
|
if(isset($tempFiles[$pagefile->basename])) {
|
|
|
|
unset($tempFiles[$pagefile->basename]);
|
|
|
|
if(count($tempFiles)) {
|
|
|
|
// set temp files back to session, minus current file
|
|
|
|
$session->set($this, $sessionKey, $tempFiles);
|
|
|
|
} else {
|
|
|
|
// if temp files is empty, we can remove it from the session
|
|
|
|
$session->remove($this, $sessionKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $isTemp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove all deletable temporary pagefiles immediately
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @return int Number of files removed
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function deleteAllTemp() {
|
|
|
|
$removed = array();
|
|
|
|
foreach($this as $pagefile) {
|
|
|
|
if(!$this->isTemp($pagefile, 'deletable')) continue;
|
|
|
|
$removed[] = $pagefile->basename();
|
|
|
|
$this->remove($pagefile);
|
|
|
|
}
|
|
|
|
if(count($removed) && $this->page && $this->field) {
|
|
|
|
$this->page->save($this->field->name, array('quiet' => true));
|
|
|
|
$this->message("Removed '{$this->field->name}' temp file(s) for page {$this->page->path} - " . implode(', ', $removed), Notice::debug | Notice::log);
|
|
|
|
}
|
|
|
|
return count($removed);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add Pagefile as item to have temporary status removed when Page is saved
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @param Pagefile $pagefile
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function unTempQueue(Pagefile $pagefile) {
|
|
|
|
$this->addSaveHook();
|
|
|
|
$this->unTempQueue[] = $pagefile;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Is the given Pagefiles identical to this one?
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @param WireArray $items
|
|
|
|
* @param bool|int $strict
|
|
|
|
* @return bool
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function isIdentical(WireArray $items, $strict = true) {
|
|
|
|
if($strict) return $this === $items;
|
|
|
|
return parent::isIdentical($items, $strict);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reset track changes
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @param bool $trackChanges
|
|
|
|
* @return $this
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function resetTrackChanges($trackChanges = true) {
|
|
|
|
$this->unlinkQueue = array();
|
|
|
|
if($this->page && $this->page->id && $this->field) {
|
|
|
|
$this->page->untrackChange($this->field->name);
|
|
|
|
}
|
|
|
|
/** @var Pagefiles $result */
|
|
|
|
$result = parent::resetTrackChanges($trackChanges);
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uncache
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function uncache() {
|
|
|
|
//$this->page = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get or set formatted state
|
|
|
|
*
|
|
|
|
* @param bool|null $set
|
|
|
|
* @return bool
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function formatted($set = null) {
|
|
|
|
if(is_bool($set)) $this->formatted = $set;
|
|
|
|
return $this->formatted;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get Template object used for Pagefile custom fields, if available (false if not)
|
|
|
|
*
|
|
|
|
* #pw-internal
|
|
|
|
*
|
|
|
|
* @return bool|Template
|
|
|
|
* @since 3.0.142
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function getFieldsTemplate() {
|
|
|
|
if($this->fieldsTemplate === null) {
|
|
|
|
/** @var Field $field */
|
|
|
|
$field = $this->getField();
|
|
|
|
if($field) {
|
|
|
|
$this->fieldsTemplate = false;
|
|
|
|
/** @var FieldtypeFile $fieldtype */
|
|
|
|
$fieldtype = $field->type;
|
|
|
|
$template = $fieldtype && $fieldtype instanceof FieldtypeFile ? $fieldtype->getFieldsTemplate($field) : null;
|
|
|
|
if($template) $this->fieldsTemplate = $template;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $this->fieldsTemplate;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get mock/placeholder Page object used for Pagefile custom fields
|
|
|
|
*
|
|
|
|
* @return Page
|
|
|
|
* @since 3.0.142
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function getFieldsPage() {
|
|
|
|
$field = $this->getField();
|
|
|
|
/** @var FieldtypeFile $fieldtype */
|
|
|
|
$fieldtype = $field->type;
|
|
|
|
return $fieldtype->getFieldsPage($field);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Debug info
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function __debugInfo() {
|
|
|
|
|
|
|
|
$info = array(
|
|
|
|
'count' => $this->count(),
|
|
|
|
'page' => $this->page ? $this->page->path() : '?',
|
|
|
|
'field' => $this->field ? $this->field->name : '?',
|
|
|
|
'url' => $this->url(),
|
|
|
|
'path' => $this->path(),
|
|
|
|
'items' => array(),
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach($this as $key => $pagefile) {
|
|
|
|
/** @var Pagefile $pagefile */
|
|
|
|
$info['items'][$key] = $pagefile->__debugInfo();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $info;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|