1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-15 03:05:26 +02:00

Update FieldtypeFile along with Pagefile and Pagefiles classes to support additional DB schema for filesize, created_users_id and modified_users_id. Also adds new related API methods.

This commit is contained in:
Ryan Cramer
2020-04-10 12:45:00 -04:00
parent 9cd3fa677b
commit 359baa14dc
6 changed files with 720 additions and 192 deletions

View File

@@ -12,7 +12,7 @@
* Pagefile objects are contained by a `Pagefiles` object.
* #pw-body
*
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
* https://processwire.com
*
* @property-read string $url URL to the file on the server.
@@ -40,6 +40,10 @@
* @property Page $page The Page object that this file is part of. #pw-group-other
* @property Field $field The Field object that this file is part of. #pw-group-other
* @property array $filedata
* @property int $created_users_id ID of user that added/uploaded the file or 0 if not known (3.0.154+). #pw-group-other
* @property int $modified_users_id ID of user that last modified the file or 0 if not known (3.0.154+). #pw-group-other
* @property User|NullPage $createdUser User that added/uploaded the file or NullPage if not known (3.0.154)+. #pw-group-other
* @property User|NullPage $modifiedUser User that last modified the file or NullPage if not known (3.0.154)+. #pw-group-other
*
* @method void install($filename)
* @method string httpUrl()
@@ -88,6 +92,30 @@ class Pagefile extends WireData {
*/
protected $fieldValues = array();
/**
* Created user (populated only on rquest)
*
* @var User|null
*
*/
protected $_createdUser = null;
/**
* Modifed user (populated only on request)
*
* @var User|null
*
*/
protected $_modifiedUser = null;
/**
* Is this a brand new Pagefile rather than one loaded from DB?
*
* @var bool
*
*/
protected $_isNew = true;
/**
* Construct a new Pagefile
*
@@ -109,6 +137,9 @@ class Pagefile extends WireData {
$this->set('formatted', false); // has an output formatter been run on this Pagefile?
$this->set('modified', 0);
$this->set('created', 0);
$this->set('filesize', 0);
$this->set('created_users_id', 0);
$this->set('modified_users_id', 0);
}
/**
@@ -119,11 +150,17 @@ class Pagefile extends WireData {
*/
public function __clone() {
$this->extras = array();
$this->set('filesize', 0);
$this->set('created_users_id', 0);
$this->set('modified_users_id', 0);
$this->_createdUser = null;
$this->_modifiedUser = null;
$this->isNew(true);
parent::__clone();
}
/**
* Set the filename associated with this Pagefile.
* Set the filename associated with this Pagefile
*
* No need to call this as it's already called from the constructor.
* This exists so that Pagefile/Pageimage descendents can create cloned variations, if applicable.
@@ -137,7 +174,10 @@ class Pagefile extends WireData {
$basename = basename($filename);
if(DIRECTORY_SEPARATOR != '/') $filename = str_replace('\\' . $basename, '/' . $basename, $filename); // To correct issue with XAMPP in Windows
if(DIRECTORY_SEPARATOR != '/') {
// To correct issue with XAMPP in Windows
$filename = str_replace('\\' . $basename, '/' . $basename, $filename);
}
if($basename != $filename && strpos($filename, $this->pagefiles->path()) !== 0) {
$this->install($filename);
@@ -198,6 +238,7 @@ class Pagefile extends WireData {
$this->wire('files')->chmod($destination);
$this->changed('file');
$this->isNew(true);
parent::set('basename', $basename);
}
@@ -215,20 +256,32 @@ class Pagefile extends WireData {
*/
public function set($key, $value) {
if($key == 'basename') {
if($key === 'basename') {
$value = $this->pagefiles->cleanBasename($value, false);
} else if($key == 'description') {
} else if($key === 'description') {
return $this->setDescription($value);
} else if($key == 'modified') {
} else if($key === 'modified') {
$value = ctype_digit("$value") ? (int) $value : strtotime($value);
} else if($key == 'created') {
} else if($key === 'created') {
$value = ctype_digit("$value") ? (int) $value : strtotime($value);
} else if($key == 'tags') {
} else if($key === 'created_users_id' || $key === 'createdUser') {
$this->setUser($value, 'created');
return $this;
} else if($key === 'modified_users_id' || $key === 'modifiedUser') {
$this->setUser($value, 'modified');
return $this;
} else if($key === 'tags') {
$this->tags($value);
return $this;
} else if($key == 'filedata') {
} else if($key === 'filedata') {
if(is_array($value)) $this->filedata($value);
return $this;
} else if($key === 'filesize') {
$value = (int) $value;
if(empty($this->data['filesize'])) {
$this->data['filesize'] = $value;
return $this;
}
}
if(strpos($key, 'description') === 0 && preg_match('/^description(\d+)$/', $value, $matches)) {
@@ -245,6 +298,64 @@ class Pagefile extends WireData {
return parent::set($key, $value);
}
/**
* Set user that created or modified this file
*
* #pw-internal
*
* @param User|int|string|true $user Specify user object, name, ID, or boolean true for current user
* @param $type 'created' or 'modified'
* @since 3.0.154
*
*/
protected function setUser($user, $type) {
$id = 0;
if($user === true) $user = $this->wire('user');
if(is_object($user)) {
if($user instanceof NullPage) {
$id = 0;
} else if($user instanceof User) {
$id = $user->isGuest() ? 0 : $user->id;
}
} else if(is_int($user)) {
$id = $user;
} else if(ctype_digit($user)) {
$id = (int) $user;
} else if(is_string($user)) {
$name = $this->wire('sanitizer')->pageName($user);
$user = $name ? $this->wire('users')->get("name=$name") : null;
$id = $user && $user->id ? $user->id : 0;
}
if($id < 0) $id = 0;
if(strpos($type, 'created') === 0) {
$this->_createdUser = ($id && $user instanceof User ? $user : null);
parent::set('created_users_id', $id);
} else if(strpos($type, 'modified') === 0) {
$this->_modifiedUser = ($id && $user instanceof User ? $user : null);
parent::set('modified_users_id', $id);
}
}
/**
* Get created/modified user
*
* #pw-internal
*
* @param string $type One of 'created' or 'modified'
* @return User|NullPage
* @since 3.0.154
*
*/
protected function getUser($type) {
$type = strpos($type, 'created') === 0 ? 'created' : 'modified';
$key = $type === 'created' ? '_createdUser' : '_modifiedUser';
if(!$this->$key) {
$id = (int) parent::get($type . '_users_id');
$this->$key = $id ? $this->wire('users')->get($id) : new NullPage();
}
return $this->$key;
}
/**
* Get or set filedata
*
@@ -550,7 +661,7 @@ class Pagefile extends WireData {
case 'created':
$value = parent::get($key);
if(empty($value)) {
$value = filemtime($this->filename());
$value = $this->filemtime();
parent::set($key, $value);
}
break;
@@ -559,16 +670,25 @@ class Pagefile extends WireData {
$value = parent::get(str_replace('Str', '', $key));
$value = wireDate($this->wire('config')->dateFormat, $value);
break;
case 'created_users_id':
case 'modified_users_id':
$value = (int) parent::get($key);
break;
case 'createdUser':
case 'modifiedUser':
$value = $this->getUser($key);
break;
case 'fileData':
case 'filedata':
$value = $this->filedata();
break;
case 'mtime':
case 'mtimeStr':
case 'filemtime':
$value = $this->filemtime();
break;
case 'mtimeStr':
case 'filemtimeStr':
$value = filemtime($this->filename());
if(strpos($key, 'Str')) $value = wireDate($this->wire('config')->dateFormat, $value);
$value = wireDate($this->wire('config')->dateFormat, $this->filemtime());
break;
case 'fieldValues':
return $this->fieldValues;
@@ -694,7 +814,7 @@ class Pagefile extends WireData {
*
*/
public function ___noCacheURL($http = false) {
return ($http ? $this->httpUrl() : $this->url()) . '?nc=' . @filemtime($this->filename());
return ($http ? $this->httpUrl() : $this->url()) . '?nc=' . $this->filemtime();
}
/**
@@ -1025,14 +1145,30 @@ class Pagefile extends WireData {
return parent::get('formatted') ? true : false;
}
/**
* Get last modified time of file
*
* @param bool $reset
* @return int Unix timestamp
* @since 3.0.154
*
*/
public function filemtime($reset = false) {
if($reset) {} // @todo
return (int) @filemtime($this->filename());
}
/**
* Returns the filesize in number of bytes.
*
* @param bool $reset
* @return int
*
*/
public function filesize() {
return @filesize($this->filename());
public function filesize($reset = false) {
if($reset) {} // @todo
$filesize = (int) @filesize($this->filename());
return $filesize;
}
/**
@@ -1158,7 +1294,8 @@ class Pagefile extends WireData {
*
*/
public function ___changed($what, $old = null, $new = null) {
if(in_array($what, array('description', 'tags', 'file'))) {
if(in_array($what, array('description', 'tags', 'file', 'filedata'))) {
$this->setUser(true, 'modified');
$this->set('modified', time());
$this->pagefiles->trackChange('item');
}
@@ -1194,6 +1331,20 @@ class Pagefile extends WireData {
return $this->pagefiles->isTemp($this, $set);
}
/**
* Get or set “new” status of the Pagefile
*
* This is true with a Pagefile that was created during this request and not loaded from DB.
*
* @param bool|null $set
* @return bool
*
*/
public function isNew($set = null) {
if(is_bool($set)) $this->_isNew = $set;
return $this->_isNew;
}
/**
* Get all extras, add an extra, or get an extra
*
@@ -1213,6 +1364,67 @@ class Pagefile extends WireData {
return isset($this->extras[$name]) ? $this->extras[$name] : null;
}
/**
* Save this Pagefile independently of the Page it lives on
*
* @return bool
* @throws WireException
* @since 3.0.154
*
*/
public function save() {
/** @var FieldtypeFile $fieldtype */
$fieldtype = $this->field->type;
return $fieldtype->saveFile($this->page, $this->field, $this);
}
/**
* Replace file with another
*
* Should be followed up with a save() to ensure related properties are also committed to DB.
*
* #pw-internal
*
* @param string $filename File to replace current one with
* @param bool $move Move given $filename rather than copy? (default=true)
* @return bool
* @throws WireException
* @since 3.0.154
*
*/
public function replaceFile($filename, $move = true) {
/** @var WireFileTools $files */
$files = $this->wire('files');
if(!is_file($filename) || !is_readable($filename)) return false;
if($move && !is_writable($filename)) $move = false;
$srcFile = $filename;
$dstFile = $this->filename();
$tmpFile = dirname($dstFile) . '/.' . basename($dstFile) . '.tmp';
if(file_exists($tmpFile)) $files->unlink($tmpFile);
$files->rename($dstFile, $tmpFile);
if($move) {
$result = $files->rename($srcFile, $dstFile);
} else {
$result = $files->copy($srcFile, $dstFile);
}
if(!$result) {
$files->rename($tmpFile, $dstFile);
return false;
}
$files->unlink($tmpFile);
$this->filesize(true);
$this->filemtime(true);
return true;
}
/**
* Ensures that isset() and empty() work for dynamic class properties
*
@@ -1242,6 +1454,8 @@ class Pagefile extends WireData {
'tags' => $this->tags,
'created' => $this->createdStr,
'modified' => $this->modifiedStr,
'created_users_id' => $this->created_users_id,
'modified_users_id' => $this->modified_users_id,
'filemtime' => $this->mtimeStr,
'filedata' => $filedata,
);

View File

@@ -772,6 +772,7 @@ class Pagefiles extends WireArray implements PageFieldValueInterface {
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;
@@ -803,6 +804,8 @@ class Pagefiles extends WireArray implements PageFieldValueInterface {
// 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;
@@ -815,6 +818,8 @@ class Pagefiles extends WireArray implements PageFieldValueInterface {
// set temporary status to false
$pagefile->created = $now;
$pagefile->modified = $now;
$pagefile->createdUser = $user;
$pagefile->modifiedUser = $user;
@touch($pagefile->filename, $now);
$isTemp = false;

View File

@@ -59,7 +59,7 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
const fileSchemaTags = 1;
/**
* File schema is configured to support 'created' date (flag)
* File schema is configured to support 'created' and 'modified' dates (flag)
*
*/
const fileSchemaDate = 2;
@@ -70,6 +70,12 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
*/
const fileSchemaFiledata = 4;
/**
* File schema is configured to store 'filesize' and created/modified users (flag)
*
*/
const fileSchemaFilesize = 8;
/**
* Flag for useTags: tags off/disabled
*
@@ -88,6 +94,14 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
*/
const useTagsPredefined = 8;
/**
* Auto-update non-present settings in DB during wakeup?
*
* @since 3.0.154 for enabling true when in development
*
*/
const autoUpdateOnWakeup = false;
/**
* Default class for Inputfield object used, auto-generated at construct
*
@@ -220,6 +234,35 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
$inputfield->destinationPath = $pagefiles->path();
}
/**
* @param Page $page
* @param Field $field
* @return array|null
*
*/
public function ___loadPageField(Page $page, Field $field) {
$n = 0;
$retry = false;
$result = null;
do {
try {
$result = parent::___loadPageField($page, $field);
} catch(\PDOException $e) {
// retry to apply new schema (this can eventually be removed)
$fileSchema = (int) $field->get('fileSchema');
// 42S22=Column not found
if($e->getCode() !== '42S22' || $n > 0 || !($fileSchema & self::fileSchemaFilesize)) throw $e;
$field->set('fileSchema', $fileSchema & ~self::fileSchemaFilesize);
$this->getDatabaseSchema($field);
$retry = true;
}
} while($retry && ++$n < 2);
return $result;
}
/**
* Given a raw value (value as stored in DB), return the value as it would appear in a Page object
*
@@ -236,26 +279,61 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
if(empty($value)) return $pagefiles;
if(!is_array($value) || array_key_exists('data', $value)) $value = array($value);
foreach($value as $v) {
if(empty($v['data'])) continue;
$pagefile = $this->getBlankPagefile($pagefiles, $v['data']);
$pagefile->description(true, $v['description']);
if(isset($v['modified'])) $pagefile->modified = $v['modified'];
if(isset($v['created'])) $pagefile->created = $v['created'];
if(isset($v['tags'])) $pagefile->tags = $v['tags'];
if(!empty($v['filedata'])) {
$filedata = json_decode($v['filedata'], true);
unset($filedata['ix']);
$pagefile->filedata = $filedata;
}
$pagefile->setTrackChanges(true);
$pagefiles->add($pagefile);
foreach($value as $a) {
if(empty($a['data'])) continue;
$this->wakeupFile($page, $field, $pagefiles, $a);
}
$pagefiles->resetTrackChanges(true);
return $pagefiles;
}
/**
* Wakeup individual file converting array of data to Pagefile and adding it to Pagefiles
*
* @param Page $page
* @param Field $field
* @param Pagefiles $pagefiles
* @param array $a Data from DB to create Pagefile from
* @return Pagefile The Pagefile object that was added
*
*/
protected function wakeupFile(Page $page, Field $field, Pagefiles $pagefiles, array $a) {
$pagefile = $this->getBlankPagefile($pagefiles, $a['data']);
$pagefile->description(true, $a['description']);
$columns = array('modified', 'created', 'tags', 'modified_users_id', 'created_users_id');
foreach($columns as $column) {
if(isset($a[$column])) $pagefile->set($column, $a[$column]);
}
if(!empty($a['filedata'])) {
$filedata = json_decode($a['filedata'], true);
unset($filedata['ix']);
$pagefile->filedata = $filedata;
}
$pagefile->isNew(false);
$pagefile->setTrackChanges(true);
$pagefiles->add($pagefile);
if(!empty($a['filesize'])) {
// @todo
// $pagefile->setQuietly('filesize', (int) $a['filesize']);
} else if(((int) $field->get('fileSchema')) & self::fileSchemaFilesize) {
// populate file size into DB row
if(self::autoUpdateOnWakeup) {
$this->saveFileCols($page, $field, $pagefile, array('filesize' => $pagefile->filesize()));
}
}
return $pagefile;
}
/**
* Given an 'awake' value, as set by wakeupValue, convert the value back to a basic type for storage in DB.
*
@@ -272,37 +350,76 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
foreach($value as $pagefile) {
/** @var Pagefile $pagefile */
$item = array(
'data' => $pagefile->basename,
'description' => $pagefile->description(true),
);
$fileSchema = $field->get('fileSchema');
if($fileSchema & self::fileSchemaDate) {
$item['modified'] = date('Y-m-d H:i:s', $pagefile->modified);
$item['created'] = date('Y-m-d H:i:s', $pagefile->created);
}
if($fileSchema & self::fileSchemaTags) {
$item['tags'] = $pagefile->tags;
}
if($fileSchema & self::fileSchemaFiledata) {
$filedata = $this->sleepFiledata($field, $pagefile);
if(empty($filedata)) {
$item['filedata'] = null;
} else {
$item['filedata'] = json_encode($filedata);
}
}
$item = $this->sleepFile($page, $field, $pagefile);
$sleepValue[] = $item;
}
return $sleepValue;
}
/**
* Convert individual Pagefile to array for storage in DB
*
* @param Page $page
* @param Field $field
* @param Pagefile $pagefile
* @return array
*
*/
protected function sleepFile(Page $page, Field $field, Pagefile $pagefile) {
if($page) {} // ignore
$isNew = $pagefile->isNew();
$isChanged = $pagefile->isChanged();
if($isNew) {
$pagefile->createdUser = $this->wire('user');
$pagefile->created = time();
}
if($isChanged || $isNew) {
$changes = array_flip($pagefile->getChanges());
unset($changes['hash'], $changes['sort'], $changes['modified'], $changes['modified_users_id']);
if($isNew || count($changes)) {
$pagefile->modifiedUser = $this->wire('user');
$pagefile->modified = time();
}
}
$item = array(
'data' => $pagefile->basename,
'description' => $pagefile->description(true),
);
$fileSchema = (int) $field->get('fileSchema');
if($fileSchema & self::fileSchemaDate) {
$item['modified'] = date('Y-m-d H:i:s', $pagefile->modified);
$item['created'] = date('Y-m-d H:i:s', $pagefile->created);
}
if($fileSchema & self::fileSchemaFilesize) {
$item['filesize'] = $pagefile->filesize(true);
$item['modified_users_id'] = (int) $pagefile->modified_users_id;
$item['created_users_id'] = (int) $pagefile->created_users_id;
}
if($fileSchema & self::fileSchemaTags) {
$item['tags'] = $pagefile->tags;
}
if($fileSchema & self::fileSchemaFiledata) {
$filedata = $this->sleepFiledata($field, $pagefile);
if(empty($filedata)) {
$item['filedata'] = null;
} else {
$item['filedata'] = json_encode($filedata);
}
}
return $item;
}
/**
* Get the filedata from given $pagefile ready for placement in a sleep value
*
@@ -666,19 +783,70 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
*/
public function getMatchQuery($query, $table, $subfield, $operator, $value) {
if($subfield && !in_array($subfield, array('data', 'count', 'filedata', 'created', 'modified', 'description', 'tags'))) {
// match custom fields
if($this->getMatchQuerySubfield($query, $subfield, $operator, $value)) {
// successfully handled
$field = $query->field;
$schema = $this->getDatabaseSchema($field);
$compareType = Selectors::getOperators(array('valueType' => 'compareType', 'operator' => $operator));
$isFindOperator = $compareType & Selector::compareTypeFind;
$isInvalidOperator = false;
$isInvalidSubfield = false;
unset($schema['keys'], $schema['xtra']);
if($subfield) {
if($subfield === 'created' || $subfield === 'modified') {
// created or modified date
if($isFindOperator) {
$isInvalidOperator = true;
} else if(ctype_digit(ltrim("$value", "-"))) {
$value = date('Y-m-d H:i:s', (int) $value);
} else {
$value = new \DateTime($value);
$value = $value->format('Y-m-d H:i:s');
}
} else if($subfield === 'modified_users_id' || $subfield === 'created_users_id') {
if($isFindOperator) {
$isInvalidOperator = true;
} else if(ctype_digit("$value")) {
$value = (int) $value;
} else {
$value = $this->wire('users')->get('name=' . $this->wire('sanitizer')->pageName($value))->id;
if(!$value) {
$operator = '=';
$value = -1;
}
}
} else if($subfield === 'count') {
// count match
if($isFindOperator) $isInvalidOperator = true;
$value = (int) $value;
} else if(isset($schema[$subfield])) {
// subfield is a column native to table
$useInt = stripos($schema[$subfield], 'int') === 0;
$useFloat = !$useInt && stripos($schema[$subfield], 'float') === 0;
if(($useInt || $useFloat) && $isFindOperator) $isInvalidOperator = true;
if($useInt) $value = (int) $value;
if($useFloat) $value = (float) $value;
} else if($this->getMatchQuerySubfield($query, $subfield, $operator, $value)) {
// match custom fields, successfully handled
} else {
// requested subfield does not match what's available
throw new PageFinderSyntaxException("Property '$subfield' not recognized in selector: $query->selector");
// requested subfield does not match whats available
$isInvalidSubfield = true;
}
}
if(in_array($operator, array("*=", "~=", "!~=", "%=", "^=", "$="))) {
// fulltext match filename or description
/** @var DatabaseQuerySelectFulltext $ft */
if($isInvalidSubfield) {
throw new PageFinderSyntaxException("Property '$subfield' not recognized in: $query->selector");
} else if($isInvalidOperator) {
throw new PageFinderSyntaxException("Invalid operator '$operator' for: $field->name.$subfield");
} else if($isFindOperator) {
/** @var DatabaseQuerySelectFulltext $ft Fulltext match filename or description */
$ft = $this->wire(new DatabaseQuerySelectFulltext($query));
$ft->match($table, $subfield, $operator, $value);
@@ -800,7 +968,6 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
$database = $this->wire('database');
$schema = parent::getDatabaseSchema($field);
$table = $database->escapeTable($field->table);
$maxLen = $database->getMaxIndexLength();
$schema['data'] = "varchar($maxLen) NOT NULL";
@@ -808,159 +975,175 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
$schema['modified'] = "datetime";
$schema['created'] = "datetime";
$schema['filedata'] = "mediumtext";
$schema['filesize'] = "int"; // 3.0.154+
$schema['created_users_id'] = 'int unsigned not null default 0'; // 3.0.154+
$schema['modified_users_id'] = 'int unsigned not null default 0'; // 3.0.154+
$schema['keys']['description'] = 'FULLTEXT KEY description (description)';
$schema['keys']['filedata'] = 'FULLTEXT KEY filedata (filedata)';
$schema['keys']['modified'] = 'index (modified)';
$schema['keys']['created'] = 'index (created)';
$schema['keys']['filesize'] = 'index (filesize)'; // 3.0.154+
if($field->id && !($field->get('fileSchema') & self::fileSchemaFiledata)) {
// permanently add new 'filedata' column to schema
$addFiledata = false;
if($field->flags & Field::flagFieldgroupContext) $field = $this->wire('fields')->get($field->name);
try {
$query = $database->prepare("SHOW COLUMNS FROM `$table` WHERE Field='filedata'");
$query->execute();
$numRows = (int) $query->rowCount();
$query->closeCursor();
if($numRows) {
$field->set('fileSchema', $field->get('fileSchema') | self::fileSchemaFiledata);
$field->save();
} else {
$addFiledata = true;
}
} catch(\Exception $e) {
// intentionally blank
if($field->id) {
if($field->flags & Field::flagFieldgroupContext) {
$field = $this->wire('fields')->get($field->name);
}
if($addFiledata) try {
$database->exec("ALTER TABLE `{$table}` ADD `filedata` $schema[filedata]");
$database->exec("ALTER TABLE `{$table}` ADD " . $schema['keys']['filedata']);
$field->set('fileSchema', $field->get('fileSchema') | self::fileSchemaFiledata);
$fileSchema1 = (int) $field->get('fileSchema');
$fileSchema2 = $this->updateDatabaseSchema($field, $schema, $fileSchema1);
if($fileSchema1 !== $fileSchema2) {
// update fileSchema flags
$field->set('fileSchema', $fileSchema2);
$field->save();
$this->message("Added filedata to DB schema for '{$field->name}'", Notice::log);
} catch(\Exception $e) {
$query = $database->prepare("SHOW COLUMNS FROM `$table` WHERE Field='filedata'");
$query->execute();
$numRows = (int) $query->rowCount();
$query->closeCursor();
if($numRows) {
$field->set('fileSchema', $field->get('fileSchema') | self::fileSchemaFiledata);
$field->save();
} else {
$this->error("Error adding created/modified to '{$field->name}' schema", Notice::log);
unset($schema['filedata'], $schema['keys']['filedata']);
}
}
}
if($field->id && !($field->get('fileSchema') & self::fileSchemaDate)) {
// permanently add new 'modified' and 'created' column to file schema
$addDates = false;
if($field->flags & Field::flagFieldgroupContext) $field = $this->wire('fields')->get($field->name);
return $schema;
}
try {
$query = $database->prepare("SHOW COLUMNS FROM `$table` WHERE Field='modified'");
$query->execute();
$numRows = (int) $query->rowCount();
$query->closeCursor();
/**
* Check and update database schema according to current version and features
*
* @param Field $field
* @param array $schema Updated directly
* @param int $fileSchema The fileSchema version flags integer
* @return int Updated fileSchema flags integer
* @since 3.0.154
*
*/
protected function updateDatabaseSchema(Field $field, array &$schema, $fileSchema) {
if($numRows) {
$field->set('fileSchema', $field->get('fileSchema') | self::fileSchemaDate);
$field->save();
} else {
$addDates = true;
}
} catch(\Exception $e) {
// intentionally blank
$contextField = $field;
if($field->flags & Field::flagFieldgroupContext) $field = $this->wire('fields')->get($field->name);
/** @var WireDatabasePDO $database */
$database = $this->wire('database');
$table = $database->escapeTable($field->table);
$hasFilesize = $fileSchema & self::fileSchemaFilesize;
$hasFiledata = $fileSchema & self::fileSchemaFiledata;
$hasDate = $fileSchema & self::fileSchemaDate;
$hasTags = $fileSchema & self::fileSchemaTags;
$useTags = $field->get('useTags') || $contextField->get('useTags');
// Filesize update (3.0.154): Adds 'filesize', 'created_users_id', 'modified_users_id' columns
if(!$hasFilesize) {
$columns = array('filesize', 'created_users_id', 'modified_users_id');
$numErrors = 0;
foreach($columns as $column) {
if(!$this->addColumn($field, $column, $schema)) $numErrors++;
if($numErrors) break;
}
if(!$numErrors) {
$fileSchema = $fileSchema | self::fileSchemaFilesize;
}
}
if($addDates) try {
$database->exec("ALTER TABLE `{$table}` ADD `modified` $schema[modified]");
$database->exec("ALTER TABLE `{$table}` ADD `created` $schema[created]");
$database->exec("ALTER TABLE `{$table}` ADD " . $schema['keys']['modified']);
$database->exec("ALTER TABLE `{$table}` ADD " . $schema['keys']['created']);
$field->set('fileSchema', $field->get('fileSchema') | self::fileSchemaDate);
$field->save();
// Filedata update: adds 'filedata' column
if(!$hasFiledata && $this->addColumn($field, 'filedata', $schema)) {
$fileSchema = $fileSchema | self::fileSchemaFiledata;
}
// Date update: Adds 'modified', 'created' columns
if(!$hasDate) {
$numErrors = 0;
if(!$this->addColumn($field, 'created', $schema)) $numErrors++;
if(!$numErrors && !$this->addColumn($field, 'modified', $schema)) $numErrors++;
if(!$numErrors) {
$fileSchema = $fileSchema | self::fileSchemaDate;
// now populate initial dates
$date = date('Y-m-d H:i:s');
$query = $database->prepare("UPDATE `$table` SET created=:created, modified=:modified");
$query = $database->prepare("UPDATE `{$table}` SET created=:created, modified=:modified");
$query->bindValue(":created", $date);
$query->bindValue(":modified", $date);
$query->execute();
$this->message("Added created/modified to DB schema for '{$field->name}'", Notice::log);
} catch(\Exception $e) {
$query = $database->prepare("SHOW COLUMNS FROM `$table` WHERE Field='modified'");
$query->execute();
$numRows = (int) $query->rowCount();
$query->closeCursor();
if($numRows) {
$field->set('fileSchema', $field->get('fileSchema') | self::fileSchemaDate);
$field->save();
} else {
$this->error("Error adding created/modified to '{$field->name}' schema", Notice::log);
try {
$query->execute();
$this->message("Populated initial created/modified dates for '{$field->name}'", Notice::log);
} catch(\Exception $e) {
$this->error("Error populating created/modified dates to '{$field->name}'", Notice::log);
}
}
}
$tagsAction = null; // null=no change; 1=add tags, 0=remove tags
// Tags update: adds 'tags' column
$schemaTags = 'varchar(250) NOT NULL';
$schemaTagsIndex = 'FULLTEXT KEY tags (tags)';
if($field->get('useTags') && !($field->get('fileSchema') & self::fileSchemaTags)) {
$tagsAction = 'add';
} else if(!$field->get('useTags') && ($field->get('fileSchema') & self::fileSchemaTags)) {
$tagsAction = 'remove';
}
if($tagsAction === 'add') {
if($useTags && !$hasTags) {
// add tags field
try {
$query = $database->prepare("SHOW COLUMNS FROM `$table` WHERE Field='tags'");
$query->execute();
$numRows = (int) $query->rowCount();
$query->closeCursor();
} catch(\Exception $e) {
// probably in a clone, we can ignore and skip over any further changes
$numRows = 1;
}
if(!$numRows) try {
if($database->columnExists($table, 'tags')) {
// congrats
$fileSchema = $fileSchema | self::fileSchemaTags;
} else try {
$database->exec("ALTER TABLE `{$table}` ADD tags $schemaTags");
$database->exec("ALTER TABLE `{$table}` ADD $schemaTagsIndex");
$field->set('fileSchema', $field->get('fileSchema') | self::fileSchemaTags);
$field->save();
$this->message("Added tags to DB schema for '{$field->name}'", Notice::log);
$fileSchema = $fileSchema | self::fileSchemaTags;
} catch(\Exception $e) {
$this->error("Error adding tags to '{$field->name}' schema", Notice::log);
}
} else if($tagsAction === 'remove') {
} else if(!$useTags && $hasTags) {
// remove tags field
$fileSchema = $fileSchema & ~self::fileSchemaTags;
/*
try {
$database->exec("ALTER TABLE `{$table}` DROP INDEX tags");
$database->exec("ALTER TABLE `{$table}` DROP tags");
$field->set('fileSchema', $field->get('fileSchema') & ~self::fileSchemaTags);
$field->save();
$this->message("Dropped tags from DB schema for '{$field->name}'", Notice::log);
$fileSchema = $fileSchema & ~self::fileSchemaTags;
} catch(\Exception $e) {
$this->error("Error dropping tags from '{$field->name}' schema", Notice::log);
}
*/
}
if($field->get('fileSchema') & self::fileSchemaTags) {
if($fileSchema & self::fileSchemaTags) {
$schema['tags'] = $schemaTags;
$schema['keys']['tags'] = $schemaTagsIndex;
}
return $schema;
return $fileSchema;
}
/**
* Adds a column
*
* @param Field $field
* @param string $column
* @param array $schema Schema array
* @return bool
* @throws WireException
*
*/
protected function addColumn(Field $field, $column, array &$schema) {
/** @var WireDatabasePDO $database */
$database = $this->wire('database');
if($database->columnExists($field->table, $column)) return true;
if(!isset($schema[$column])) throw new WireException("Missing schema for $field->name.$column");
$table = $database->escapeTable($field->table);
try {
$result = $database->exec("ALTER TABLE `{$table}` ADD `$column` $schema[$column]");
if($result) {
if(isset($schema['keys'][$column])) {
$database->exec("ALTER TABLE `{$table}` ADD " . $schema['keys'][$column]);
}
$this->message("Added '$column' to DB schema for '$field->name'", Notice::log | Notice::debug);
}
} catch(\Exception $e) {
if($database->columnExists($table, $column)) {
$result = true;
} else {
$this->error("Error adding '$column' to '{$field->name}' schema", Notice::log);
unset($schema[$column], $schema['keys'][$column]);
$result = false;
}
}
return $result;
}
@@ -1069,6 +1252,128 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
return $page;
}
/**
* Save a single Pagefile to DB
*
* #pw-internal
*
* @param Page $page
* @param Field $field
* @param Pagefile $pagefile
* @param array $columns Update only these column names (for existing Pagefile only)
* @return bool
* @throws WireException
* @since 3.0.154
*
*/
public function saveFile(Page $page, Field $field, Pagefile $pagefile, array $columns = array()) {
$item = $this->sleepFile($page, $field, $pagefile);
$database = $this->wire('database'); /** @var WireDatabasePDO $database */
$table = $database->escapeTable($field->getTable());
$sets = array();
$binds = array(':pages_id' => $page->id);
$isNew = $pagefile->isNew();
$columns = !$isNew && count($columns) ? array_flip($columns) : null;
if($pagefile->formatted()) {
if(!$columns || isset($columns['filedata']) || isset($columns['description'])) {
throw new WireException("Cannot save formatted Pagefile: $pagefile");
}
}
foreach($item as $key => $value) {
if($columns && !isset($columns[$key])) continue;
$sets[] = "$key=:$key" ;
$binds[":$key"] = $value;
}
$sets = implode(', ', $sets);
if($isNew) {
$sql = "INSERT INTO $table SET pages_id=:pages_id, sort=:sort, $sets";
$binds[":sort"] = $this->getMaxColumnValue($page, $field, 'sort');
} else {
$sql = "UPDATE $table SET $sets WHERE pages_id=:pages_id AND data=:name";
$binds[':name'] = $pagefile->name;
}
$query = $database->prepare($sql);
foreach($binds as $key => $value) {
if($value === null) {
$type = \PDO::PARAM_NULL;
} else if(is_int($value)) {
$type = \PDO::PARAM_INT;
} else {
$type = \PDO::PARAM_STR;
}
$query->bindValue($key, $value, $type);
}
$result = $query->execute() ? $query->rowCount() : 0;
if($isNew && $result) $pagefile->isNew(false);
return (bool) $result;
}
/**
* Specify specific columns and values to update for Pagefile
*
* This update is performed quietly, not updating 'modified' or 'modified_users_id'
* unless specified in given data array $a.
*
* This method cannot be used to update 'description' or 'filedata' properties,
* and it will not save for Pagefile entries not already in the DB.
*
* #pw-internal
*
* @param Page $page
* @param Field $field
* @param Pagefile $pagefile
* @param array $a Associative array of column names and values
* @return bool
* @throws WireException
* @since 3.0.154
*
*/
public function saveFileCols(Page $page, Field $field, Pagefile $pagefile, array $a) {
$database = $this->wire('database'); /** @var WireDatabasePDO $database */
$table = $database->escapeTable($field->getTable());
$schema = $this->getDatabaseSchema($field);
$binds = array(':pid' => $page->id, ':name' => $pagefile->name);
$sets = array();
if($pagefile->isNew()) {
throw new WireException("Cannot saveFileCols() on “new” Pagefile $pagefile");
}
if(empty($a) || !$page->id || !($field->type instanceof FieldtypeFile)) return false;
foreach($a as $col => $value) {
if(!isset($schema[$col])) {
throw new WireException("Unknown column '$col' for field $field->name");
} else if($col === 'filedata' || strpos($col, 'description') === 0) {
throw new WireException("Column '$col' cannot be saved with $this::saveFileCols()");
}
$sets[] = "$col=:$col";
$binds[":$col"] = $value;
}
$sql = "UPDATE $table SET " . implode(', ', $sets) . " where pages_id=:pid AND data=:name";
$query = $database->prepare($sql);
foreach($binds as $key => $value) {
$query->bindValue($key, $value);
}
$result = $query->execute() ? $query->rowCount() : 0;
return (bool) $result;
}
/**
* Field config
*

View File

@@ -740,6 +740,9 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
} else {
$this->message($message);
}
$pagefile->createdUser = $this->wire('user');
$pagefile->modifiedUser = $this->wire('user');
}
protected function fileAddedGetMarkup(Pagefile $pagefile, $n) {

View File

@@ -2315,6 +2315,7 @@ function InputfieldImage($) {
initInputfield($inputfield);
initUploadHTML5($inputfield);
//console.log('InputfieldImage reloaded');
Inputfields.init($inputfield);
}).on('wiretabclick', function(e, $newTab, $oldTab) {
$newTab.find(".InputfieldImage").each(function() {
initInputfield($(this));

File diff suppressed because one or more lines are too long