mirror of
https://github.com/processwire/processwire.git
synced 2025-08-16 03:34:33 +02:00
Add support for $template->pagefileSecure option configured per-template. This also expands upon what $config->pagefileSecure could do before, now supporting the ability to secure Pagefiles even for public pages when appropriate.
This commit is contained in:
@@ -149,6 +149,7 @@
|
|||||||
*
|
*
|
||||||
* @property string $userAuthSalt Salt generated at install time to be used as a secondary/non-database salt for the password system. #pw-group-session
|
* @property string $userAuthSalt Salt generated at install time to be used as a secondary/non-database salt for the password system. #pw-group-session
|
||||||
* @property string $userAuthHashType Default is 'sha1' - used only if Blowfish is not supported by the system. #pw-group-session
|
* @property string $userAuthHashType Default is 'sha1' - used only if Blowfish is not supported by the system. #pw-group-session
|
||||||
|
* @property string $tableSalt #pw-group-system Additional hash for other (non-authentication) purposes, present only on installations start from 3.0.164+. #pw-group-system
|
||||||
*
|
*
|
||||||
* @property bool $internal This is automatically set to FALSE when PW is externally bootstrapped. #pw-group-runtime
|
* @property bool $internal This is automatically set to FALSE when PW is externally bootstrapped. #pw-group-runtime
|
||||||
* @property bool $external This is automatically set to TRUE when PW is externally bootstrapped. #pw-internal
|
* @property bool $external This is automatically set to TRUE when PW is externally bootstrapped. #pw-internal
|
||||||
|
@@ -4107,6 +4107,26 @@ class Page extends WireData implements \Countable, WireMatchable {
|
|||||||
return $this->filesManager;
|
return $this->filesManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this Page use secure Pagefiles?
|
||||||
|
*
|
||||||
|
* See also `$template->pagefileSecure` and `$config->pagefileSecure` which determine the return value.
|
||||||
|
*
|
||||||
|
* #pw-group-files
|
||||||
|
*
|
||||||
|
* @return bool|null Returns boolean true if yes, false if no, or null if not known
|
||||||
|
* @since 3.0.166
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function secureFiles() {
|
||||||
|
if($this->wire()->config->pagefileSecure && !$this->isPublic()) return true;
|
||||||
|
if(!$this->template) return null;
|
||||||
|
$value = $this->template->pagefileSecure;
|
||||||
|
if($value < 1) return false; // 0: disabled
|
||||||
|
if($value > 1) return true; // 2: files always secure
|
||||||
|
return !$this->isPublic(); // 1: secure only if page not public
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the page have a files path for storing files?
|
* Does the page have a files path for storing files?
|
||||||
*
|
*
|
||||||
|
@@ -62,6 +62,14 @@ class PagefilesManager extends Wire {
|
|||||||
const metaFileName = '.pw';
|
const metaFileName = '.pw';
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count of renamed paths when changing between pagefileSecure and non-pagefileSecure
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static $numRenamedPaths = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to the Page object this PagefilesManager is managing
|
* Reference to the Page object this PagefilesManager is managing
|
||||||
*
|
*
|
||||||
@@ -528,7 +536,7 @@ class PagefilesManager extends Wire {
|
|||||||
*/
|
*/
|
||||||
static public function _path(Page $page, $extended = false) {
|
static public function _path(Page $page, $extended = false) {
|
||||||
|
|
||||||
$config = $page->wire('config');
|
$config = $page->wire()->config;
|
||||||
$path = $config->paths->files;
|
$path = $config->paths->files;
|
||||||
|
|
||||||
$securePrefix = $config->pagefileSecurePathPrefix;
|
$securePrefix = $config->pagefileSecurePathPrefix;
|
||||||
@@ -541,39 +549,39 @@ class PagefilesManager extends Wire {
|
|||||||
$publicPath = $path . $page->id . '/';
|
$publicPath = $path . $page->id . '/';
|
||||||
$securePath = $path . $securePrefix . $page->id . '/';
|
$securePath = $path . $securePrefix . $page->id . '/';
|
||||||
}
|
}
|
||||||
/* @todo 3.0.150:
|
|
||||||
$filesPublic = true;
|
$secureFiles = $page->secureFiles();
|
||||||
if(!$page->isPublic()) {
|
|
||||||
// page not publicly viewable to all, check if files are public or not
|
if($secureFiles === false) {
|
||||||
if($config->pagefileSecure) {
|
|
||||||
$filesPublic = false;
|
|
||||||
} else if($page->template && $page->template->pagefileSecure) {
|
|
||||||
$filesPublic = false; // 3.0.150+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if($filesPublic) {
|
|
||||||
*/
|
|
||||||
if($page->isPublic() || !$config->pagefileSecure) {
|
|
||||||
// use the public path, renaming a secure path to public if it exists
|
// use the public path, renaming a secure path to public if it exists
|
||||||
if(is_dir($securePath) && !is_dir($publicPath)) {
|
if(is_dir($securePath) && !is_dir($publicPath) && $secureFiles !== null) {
|
||||||
@rename($securePath, $publicPath);
|
$page->wire()->files->rename($securePath, $publicPath);
|
||||||
|
self::$numRenamedPaths++;
|
||||||
}
|
}
|
||||||
$filesPath = $publicPath;
|
$filesPath = $publicPath;
|
||||||
|
|
||||||
|
} else if($secureFiles === null) {
|
||||||
|
$filesPath = $publicPath;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// use the secure path, renaming the public to secure if it exists
|
// use the secure path, renaming the public to secure if it exists
|
||||||
$hasSecurePath = is_dir($securePath);
|
$hasSecurePath = is_dir($securePath);
|
||||||
if(is_dir($publicPath) && !$hasSecurePath) {
|
if(is_dir($publicPath) && !$hasSecurePath) {
|
||||||
@rename($publicPath, $securePath);
|
$page->wire()->files->rename($publicPath, $securePath);
|
||||||
|
self::$numRenamedPaths++;
|
||||||
|
|
||||||
} else if(!$hasSecurePath && self::defaultSecurePathPrefix != $securePrefix) {
|
} else if(!$hasSecurePath && self::defaultSecurePathPrefix != $securePrefix) {
|
||||||
// we track this just in case the prefix was newly added to config.php, this prevents
|
// we track this just in case the prefix was newly added to config.php, this prevents
|
||||||
// losing track of the original directories
|
// losing track of the original directories
|
||||||
$securePath2 = $extended ? $path . self::_dirExtended($page->id, self::defaultSecurePathPrefix) : $path . self::defaultSecurePathPrefix . $page->id . '/';
|
if($extended) {
|
||||||
|
$securePath2 = $path . self::_dirExtended($page->id, self::defaultSecurePathPrefix);
|
||||||
|
} else {
|
||||||
|
$securePath2 = $path . self::defaultSecurePathPrefix . $page->id . '/';
|
||||||
|
}
|
||||||
if(is_dir($securePath2)) {
|
if(is_dir($securePath2)) {
|
||||||
// if the secure path prefix has been changed from undefined to defined
|
// if the secure path prefix has been changed from undefined to defined
|
||||||
@rename($securePath2, $securePath);
|
$page->wire()->files->rename($securePath2, $securePath);
|
||||||
|
self::$numRenamedPaths++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$filesPath = $securePath;
|
$filesPath = $securePath;
|
||||||
@@ -587,6 +595,22 @@ class PagefilesManager extends Wire {
|
|||||||
return $filesPath;
|
return $filesPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get quantity of renamed paths to to pagefileSecure changes
|
||||||
|
*
|
||||||
|
* #pw-internal
|
||||||
|
*
|
||||||
|
* @param bool $reset Also reset to 0?
|
||||||
|
* @return int
|
||||||
|
* @since 3.0.166
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static public function numRenamedPaths($reset = false) {
|
||||||
|
$num = self::$numRenamedPaths;
|
||||||
|
if($reset) self::$numRenamedPaths = 0;
|
||||||
|
return $num;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the directory name (after /site/assets/files/)
|
* Generate the directory name (after /site/assets/files/)
|
||||||
*
|
*
|
||||||
|
@@ -81,7 +81,7 @@
|
|||||||
* @property int|bool $noAppendTemplateFile Disabe automatic append of $config->appendTemplateFile (if in use). #pw-group-files
|
* @property int|bool $noAppendTemplateFile Disabe automatic append of $config->appendTemplateFile (if in use). #pw-group-files
|
||||||
* @property string $prependFile File to prepend to template file (separate from $config->prependTemplateFile). #pw-group-files
|
* @property string $prependFile File to prepend to template file (separate from $config->prependTemplateFile). #pw-group-files
|
||||||
* @property string $appendFile File to append to template file (separate from $config->appendTemplateFile). #pw-group-files
|
* @property string $appendFile File to append to template file (separate from $config->appendTemplateFile). #pw-group-files
|
||||||
* @property bool $pagefileSecure Use secure pagefiles for pages using this template? (3.0.150+) #pw-group-files
|
* @property int $pagefileSecure Use secure pagefiles for pages using this template? 0=No/not set, 1=Yes (for non-public pages), 2=Always (3.0.166+) #pw-group-files
|
||||||
*
|
*
|
||||||
* Page Editor
|
* Page Editor
|
||||||
*
|
*
|
||||||
@@ -272,7 +272,7 @@ class Template extends WireData implements Saveable, Exportable {
|
|||||||
'noAppendTemplateFile' => 0, // disable automatic inclusion of $config->appendTemplateFile
|
'noAppendTemplateFile' => 0, // disable automatic inclusion of $config->appendTemplateFile
|
||||||
'prependFile' => '', // file to prepend (relative to /site/templates/)
|
'prependFile' => '', // file to prepend (relative to /site/templates/)
|
||||||
'appendFile' => '', // file to append (relative to /site/templates/)
|
'appendFile' => '', // file to append (relative to /site/templates/)
|
||||||
'pagefileSecure' => false, // secure files connected with page? (3.0.150+)
|
'pagefileSecure' => 0, // secure files connected with page? 0=Off, 1=Yes for unpub/non-public pages, 2=Always (3.0.166+)
|
||||||
'tabContent' => '', // label for the Content tab (if different from 'Content')
|
'tabContent' => '', // label for the Content tab (if different from 'Content')
|
||||||
'tabChildren' => '', // label for the Children tab (if different from 'Children')
|
'tabChildren' => '', // label for the Children tab (if different from 'Children')
|
||||||
'nameLabel' => '', // label for the "name" property of the page (if something other than "Name")
|
'nameLabel' => '', // label for the "name" property of the page (if something other than "Name")
|
||||||
@@ -1376,6 +1376,23 @@ class Template extends WireData implements Saveable, Exportable {
|
|||||||
return $this->wire('templates')->getPageClass($this, $withNamespace);
|
return $this->wire('templates')->getPageClass($this, $withNamespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that all file asset paths are consistent with current pagefileSecure setting and access control
|
||||||
|
*
|
||||||
|
* #pw-internal
|
||||||
|
*
|
||||||
|
* @return int Returns quantity of renamed paths, or 0 if all is in order
|
||||||
|
* @since 3.0.166
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function checkPagefileSecure() {
|
||||||
|
PagefilesManager::numRenamedPaths(true);
|
||||||
|
foreach($this->wire()->pages->findMany("template=$this, include=all") as $p) {
|
||||||
|
PagefilesManager::_path($p);
|
||||||
|
}
|
||||||
|
return PagefilesManager::numRenamedPaths(true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the icon to use with this template
|
* Set the icon to use with this template
|
||||||
*
|
*
|
||||||
|
@@ -266,7 +266,7 @@ class PagePermissions extends WireData implements Module {
|
|||||||
} else if($this->wire('page') && $this->wire('page')->process == 'ProcessProfile') {
|
} else if($this->wire('page') && $this->wire('page')->process == 'ProcessProfile') {
|
||||||
// user editing themself in ProcessProfile, when process not yet established
|
// user editing themself in ProcessProfile, when process not yet established
|
||||||
return true;
|
return true;
|
||||||
} else if($process == 'ProcessPageView' && $config->pagefileSecure && $options['viewable']) {
|
} else if($process == 'ProcessPageView' && $page->secureFiles() && $options['viewable']) {
|
||||||
// user is viewing a file that is part of their User page when pagefileSecure mode active
|
// user is viewing a file that is part of their User page when pagefileSecure mode active
|
||||||
return $process->getResponseType() == ProcessPageView::responseTypeFile;
|
return $process->getResponseType() == ProcessPageView::responseTypeFile;
|
||||||
}
|
}
|
||||||
@@ -481,6 +481,28 @@ class PagePermissions extends WireData implements Module {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is given file viewable?
|
||||||
|
*
|
||||||
|
* It is assumed that you have already determined the Page is viewable.
|
||||||
|
*
|
||||||
|
* @param Page $page
|
||||||
|
* @param Pagefile|string $pagefile
|
||||||
|
* @return bool|null Returns bool, or null if not known
|
||||||
|
* @since 3.0.166
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function fileViewable(Page $page, $pagefile) {
|
||||||
|
if($this->wire()->user->isSuperuser()) return true;
|
||||||
|
if(!$pagefile instanceof Pagefile) {
|
||||||
|
$pagefile = $page->hasFile(basename($pagefile), array('getPagefile' => true));
|
||||||
|
if(!$pagefile) return null;
|
||||||
|
}
|
||||||
|
$field = $pagefile->field;
|
||||||
|
if(!$field) return null;
|
||||||
|
return $this->fieldViewable($page, $field, false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the given field editable by the current user in their user profile?
|
* Is the given field editable by the current user in their user profile?
|
||||||
*
|
*
|
||||||
@@ -511,6 +533,11 @@ class PagePermissions extends WireData implements Module {
|
|||||||
* - Optionally specify Language object or language name as first argument to check if viewable
|
* - Optionally specify Language object or language name as first argument to check if viewable
|
||||||
* in that language (requires LanguageSupportPageNames module).
|
* in that language (requires LanguageSupportPageNames module).
|
||||||
* - Optionally specify boolean false as first or second argument to bypass template filename check.
|
* - Optionally specify boolean false as first or second argument to bypass template filename check.
|
||||||
|
* - Optionally specify a Pagefile object or file basename to check if file is viewable. (3.0.166+)
|
||||||
|
*
|
||||||
|
* Returns boolean true or false. If given a Pagefile or file basename, it can also return null if
|
||||||
|
* the Page itself is viewable but the file did not map to something we recognize as access controlled,
|
||||||
|
* like a file basename that isn’t present in any file fields on the page.
|
||||||
*
|
*
|
||||||
* @param HookEvent $event
|
* @param HookEvent $event
|
||||||
*
|
*
|
||||||
@@ -520,11 +547,12 @@ class PagePermissions extends WireData implements Module {
|
|||||||
/** @var Page $page */
|
/** @var Page $page */
|
||||||
$page = $event->object;
|
$page = $event->object;
|
||||||
$viewable = true;
|
$viewable = true;
|
||||||
$user = $this->wire('user');
|
$user = $this->wire()->user;
|
||||||
$arg0 = $event->arguments(0);
|
$arg0 = $event->arguments(0);
|
||||||
$arg1 = $event->arguments(1);
|
$arg1 = $event->arguments(1);
|
||||||
$field = null; // field name or Field object, if specified as arg0
|
$field = null; // field name or Field object, if specified as arg0
|
||||||
$checkFile = true; // return false if template filename doesn't exist
|
$checkTemplateFile = true; // return false if template filename doesn't exist
|
||||||
|
$pagefile = null;
|
||||||
$status = $page->status;
|
$status = $page->status;
|
||||||
|
|
||||||
// allow specifying User instance as argument 0
|
// allow specifying User instance as argument 0
|
||||||
@@ -533,16 +561,21 @@ class PagePermissions extends WireData implements Module {
|
|||||||
if($arg0 instanceof User) {
|
if($arg0 instanceof User) {
|
||||||
// user specified
|
// user specified
|
||||||
$user = $arg0;
|
$user = $arg0;
|
||||||
|
} else if($arg0 instanceof Pagefile || (is_string($arg0) && strpos($arg0, '.'))) {
|
||||||
|
// Pagefile or file basename
|
||||||
|
$pagefile = $arg0;
|
||||||
|
$checkTemplateFile = false;
|
||||||
} else if($arg0 instanceof Field || is_string($arg0)) {
|
} else if($arg0 instanceof Field || is_string($arg0)) {
|
||||||
// field name, Field object or language name specified
|
// field name, Field object or language name specified
|
||||||
// @todo: prevent possible collision of field name and language name
|
// @todo: prevent possible collision of field name and language name
|
||||||
$field = $arg0;
|
$field = $arg0;
|
||||||
$checkFile = false;
|
$checkTemplateFile = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($arg0 === false || $arg1 === false) {
|
if($arg0 === false || $arg1 === false) {
|
||||||
// bypass template filename check
|
// bypass template filename check
|
||||||
$checkFile = false;
|
$checkTemplateFile = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if page has corrupted status, this need not affect viewable access
|
// if page has corrupted status, this need not affect viewable access
|
||||||
@@ -552,7 +585,7 @@ class PagePermissions extends WireData implements Module {
|
|||||||
if($status >= Page::statusUnpublished) {
|
if($status >= Page::statusUnpublished) {
|
||||||
// unpublished pages are not viewable, but see override below this if/else statement
|
// unpublished pages are not viewable, but see override below this if/else statement
|
||||||
$viewable = false;
|
$viewable = false;
|
||||||
} else if(!$page->template || ($checkFile && !$page->template->filenameExists())) {
|
} else if(!$page->template || ($checkTemplateFile && !$page->template->filenameExists())) {
|
||||||
// template file does not exist
|
// template file does not exist
|
||||||
$viewable = false;
|
$viewable = false;
|
||||||
} else if($user->isSuperuser()) {
|
} else if($user->isSuperuser()) {
|
||||||
@@ -574,11 +607,13 @@ class PagePermissions extends WireData implements Module {
|
|||||||
|
|
||||||
// if the page is editable by the current user, force it to be viewable (if not viewable due to being unpublished)
|
// if the page is editable by the current user, force it to be viewable (if not viewable due to being unpublished)
|
||||||
if(!$viewable && !$user->isGuest() && ($status & Page::statusUnpublished)) {
|
if(!$viewable && !$user->isGuest() && ($status & Page::statusUnpublished)) {
|
||||||
if($page->editable() && (!$checkFile || $page->template->filenameExists())) $viewable = true;
|
if($page->editable() && (!$checkTemplateFile || $page->template->filenameExists())) $viewable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($field && $viewable) {
|
if($field && $viewable) {
|
||||||
$viewable = $this->fieldViewable($page, $field, false);
|
$viewable = $this->fieldViewable($page, $field, false);
|
||||||
|
} else if($pagefile && $viewable) {
|
||||||
|
$viewable = $this->fileViewable($page, $pagefile);
|
||||||
}
|
}
|
||||||
|
|
||||||
$event->return = $viewable;
|
$event->return = $viewable;
|
||||||
|
@@ -29,7 +29,7 @@ class ProcessPageView extends Process {
|
|||||||
'version' => 104,
|
'version' => 104,
|
||||||
'permanent' => true,
|
'permanent' => true,
|
||||||
'permission' => 'page-view',
|
'permission' => 'page-view',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -372,8 +372,7 @@ class ProcessPageView extends Process {
|
|||||||
$numParts = substr_count($it, '/');
|
$numParts = substr_count($it, '/');
|
||||||
if($numParts > $config->maxUrlDepth) return null;
|
if($numParts > $config->maxUrlDepth) return null;
|
||||||
|
|
||||||
// if($this->isSecurePagefileUrl($it)) { // @todo replace next line with this in 3.0.150
|
if($this->pagefileSecurePossible($it)) {
|
||||||
if($config->pagefileSecure) {
|
|
||||||
$page = $this->checkRequestFile($it);
|
$page = $this->checkRequestFile($it);
|
||||||
if(is_object($page)) {
|
if(is_object($page)) {
|
||||||
$this->responseType = self::responseTypeFile;
|
$this->responseType = self::responseTypeFile;
|
||||||
@@ -482,10 +481,8 @@ class ProcessPageView extends Process {
|
|||||||
*/
|
*/
|
||||||
protected function checkRequestFile(&$it) {
|
protected function checkRequestFile(&$it) {
|
||||||
|
|
||||||
/** @var Config $config */
|
$config = $this->wire()->config;
|
||||||
$config = $this->wire('config');
|
$pages = $this->wire()->pages;
|
||||||
/** @var Pages $pages */
|
|
||||||
$pages = $this->wire('pages');
|
|
||||||
|
|
||||||
// request with url to root (applies only if site runs from subdirectory)
|
// request with url to root (applies only if site runs from subdirectory)
|
||||||
$itRoot = rtrim($config->urls->root, '/') . $it;
|
$itRoot = rtrim($config->urls->root, '/') . $it;
|
||||||
@@ -501,11 +498,14 @@ class ProcessPageView extends Process {
|
|||||||
$idPath = trim($matches[1], '/');
|
$idPath = trim($matches[1], '/');
|
||||||
$file = trim($matches[2], '.');
|
$file = trim($matches[2], '.');
|
||||||
|
|
||||||
|
if(!strpos($file, '.')) return $pages->newNullPage();
|
||||||
|
|
||||||
if(!ctype_digit("$idPath")) {
|
if(!ctype_digit("$idPath")) {
|
||||||
// extended paths where id separated by slashes, i.e. 1/2/3/4
|
// extended paths where id separated by slashes, i.e. 1/2/3/4
|
||||||
if($config->pagefileExtendedPaths) {
|
if($config->pagefileExtendedPaths) {
|
||||||
// allow extended paths
|
// allow extended paths
|
||||||
$idPath = str_replace('/', '', $matches[1]);
|
$idPath = str_replace('/', '', $matches[1]);
|
||||||
|
if(!ctype_digit("$idPath")) return $pages->newNullPage();
|
||||||
} else {
|
} else {
|
||||||
// extended paths not allowed
|
// extended paths not allowed
|
||||||
return $pages->newNullPage();
|
return $pages->newNullPage();
|
||||||
@@ -525,7 +525,7 @@ class ProcessPageView extends Process {
|
|||||||
return $pages->newNullPage();
|
return $pages->newNullPage();
|
||||||
|
|
||||||
} else if(!preg_match('/^[a-zA-Z0-9][-_a-zA-Z0-9]+$/', $subdir)) {
|
} else if(!preg_match('/^[a-zA-Z0-9][-_a-zA-Z0-9]+$/', $subdir)) {
|
||||||
// subdirectory nat in expected format
|
// subdirectory not in expected format
|
||||||
return $pages->newNullPage();
|
return $pages->newNullPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,6 +702,7 @@ class ProcessPageView extends Process {
|
|||||||
|
|
||||||
if($this->requestFile) {
|
if($this->requestFile) {
|
||||||
// if a file was requested, we still allow view even if page doesn't have template file
|
// if a file was requested, we still allow view even if page doesn't have template file
|
||||||
|
if($page->viewable($this->requestFile) === false) return null;
|
||||||
if($page->viewable(false)) return $page;
|
if($page->viewable(false)) return $page;
|
||||||
// if($page->editable()) return $page;
|
// if($page->editable()) return $page;
|
||||||
if($this->checkAccessDelegated($page)) return $page;
|
if($this->checkAccessDelegated($page)) return $page;
|
||||||
@@ -863,29 +864,37 @@ class ProcessPageView extends Process {
|
|||||||
* If the page is public, then it just does a 301 redirect to the file.
|
* If the page is public, then it just does a 301 redirect to the file.
|
||||||
*
|
*
|
||||||
* @param Page $page
|
* @param Page $page
|
||||||
* @param string $basename
|
* @param string $basename
|
||||||
|
* @param array $options
|
||||||
* @throws Wire404Exception
|
* @throws Wire404Exception
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
protected function ___sendFile($page, $basename) {
|
protected function ___sendFile($page, $basename, array $options = array()) {
|
||||||
|
|
||||||
$err = 'File not found';
|
$err = 'File not found';
|
||||||
|
|
||||||
// use the static hasPath first to make sure this page actually has a files directory
|
if(!$page->hasFilesPath()) {
|
||||||
// this ensures one isn't automatically created when we call $page->filesManager->path below
|
throw new Wire404Exception($err, Wire404Exception::codeFile);
|
||||||
if(!PagefilesManager::hasPath($page)) throw new Wire404Exception($err, Wire404Exception::codeFile);
|
|
||||||
|
|
||||||
$filename = $page->filesManager->path() . $basename;
|
|
||||||
if(!is_file($filename)) throw new Wire404Exception($err, Wire404Exception::codeFile);
|
|
||||||
|
|
||||||
if($page->isPublic()) {
|
|
||||||
// deprecated, only necessary for method 2 in checkRequestFile
|
|
||||||
$this->wire('session')->redirect($page->filesManager->url() . $basename);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$options = array('exit' => false);
|
|
||||||
wireSendFile($filename, $options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$filename = $page->filesPath() . $basename;
|
||||||
|
|
||||||
|
if(!file_exists($filename)) {
|
||||||
|
throw new Wire404Exception($err, Wire404Exception::codeFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$page->secureFiles()) {
|
||||||
|
// if file is not secured, redirect to it
|
||||||
|
// (potentially deprecated, only necessary for method 2 in checkRequestFile)
|
||||||
|
$this->wire()->session->redirect($page->filesManager->url() . $basename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// options for WireHttp::sendFile
|
||||||
|
$defaults = array('exit' => false);
|
||||||
|
$options = array_merge($defaults, $options);
|
||||||
|
|
||||||
|
$this->wire()->files->send($filename, $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -971,10 +980,11 @@ class ProcessPageView extends Process {
|
|||||||
*
|
*
|
||||||
* @param string $url
|
* @param string $url
|
||||||
* @return bool
|
* @return bool
|
||||||
* @todo enable in 3.0.150
|
* @since 3.0.166
|
||||||
*
|
*
|
||||||
protected function isSecurePagefileUrl($url) {
|
*/
|
||||||
$config = $this->wire('config');
|
protected function pagefileSecurePossible($url) {
|
||||||
|
$config = $this->wire()->config;
|
||||||
|
|
||||||
// if URL does not start from root, prepend root
|
// if URL does not start from root, prepend root
|
||||||
if(strpos($url, $config->urls->root) !== 0) $url = $config->urls->root . ltrim($url, '/');
|
if(strpos($url, $config->urls->root) !== 0) $url = $config->urls->root . ltrim($url, '/');
|
||||||
@@ -987,7 +997,7 @@ class ProcessPageView extends Process {
|
|||||||
|
|
||||||
// check if any templates allow pagefileSecure option
|
// check if any templates allow pagefileSecure option
|
||||||
$allow = false;
|
$allow = false;
|
||||||
foreach($this->wire('templates') as $template) {
|
foreach($this->wire()->templates as $template) {
|
||||||
if(!$template->pagefileSecure) continue;
|
if(!$template->pagefileSecure) continue;
|
||||||
$allow = true;
|
$allow = true;
|
||||||
break;
|
break;
|
||||||
@@ -996,7 +1006,6 @@ class ProcessPageView extends Process {
|
|||||||
// if at least one template supports pagefileSecure option we will return true here
|
// if at least one template supports pagefileSecure option we will return true here
|
||||||
return $allow;
|
return $allow;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -684,6 +684,7 @@ class ProcessTemplate extends Process {
|
|||||||
$t->add($this->buildEditFormAccess($template));
|
$t->add($this->buildEditFormAccess($template));
|
||||||
$overrides = $this->buildEditFormAccessOverrides($template);
|
$overrides = $this->buildEditFormAccessOverrides($template);
|
||||||
if($overrides) $t->add($overrides);
|
if($overrides) $t->add($overrides);
|
||||||
|
$t->add($this->buildEditFormAccessFiles($template));
|
||||||
$form->add($t);
|
$form->add($t);
|
||||||
|
|
||||||
$t = $this->wire(new InputfieldWrapper());
|
$t = $this->wire(new InputfieldWrapper());
|
||||||
@@ -1246,6 +1247,7 @@ class ProcessTemplate extends Process {
|
|||||||
$form = $this->wire(new InputfieldWrapper());
|
$form = $this->wire(new InputfieldWrapper());
|
||||||
/** @var Languages|null $languages */
|
/** @var Languages|null $languages */
|
||||||
$languages = $this->wire('languages');
|
$languages = $this->wire('languages');
|
||||||
|
$advanced = $this->wire()->config->advanced;
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
|
|
||||||
@@ -1392,7 +1394,7 @@ class ProcessTemplate extends Process {
|
|||||||
$field->attr('name', 'noChangeTemplate');
|
$field->attr('name', 'noChangeTemplate');
|
||||||
$field->label = $this->_("Don't allow pages to change their template?");
|
$field->label = $this->_("Don't allow pages to change their template?");
|
||||||
$field->description = $this->_("When checked, pages using this template will be unable to change to another template."); // noChangeTemplate option, description
|
$field->description = $this->_("When checked, pages using this template will be unable to change to another template."); // noChangeTemplate option, description
|
||||||
if($this->wire('config')->advanced) $field->notes = 'API: $template->noChangeTemplate = 1; // ' . $label0;
|
if($advanced) $field->notes = 'API: $template->noChangeTemplate = 1; // ' . $label0;
|
||||||
|
|
||||||
$field->attr('value', 1);
|
$field->attr('value', 1);
|
||||||
if($template->noChangeTemplate) {
|
if($template->noChangeTemplate) {
|
||||||
@@ -1409,7 +1411,7 @@ class ProcessTemplate extends Process {
|
|||||||
$field->attr('name', 'noUnpublish');
|
$field->attr('name', 'noUnpublish');
|
||||||
$field->label = $this->_("Don't allow unpublished pages");
|
$field->label = $this->_("Don't allow unpublished pages");
|
||||||
$field->description = $this->_("When checked, pages using this template may only exist in a published state and may not be unpublished."); // noUnpublish option, description
|
$field->description = $this->_("When checked, pages using this template may only exist in a published state and may not be unpublished."); // noUnpublish option, description
|
||||||
if($this->wire('config')->advanced) $field->notes = 'API: $template->noUnpublish = 1; // ' . $label0;
|
if($advanced) $field->notes = 'API: $template->noUnpublish = 1; // ' . $label0;
|
||||||
|
|
||||||
$field->attr('value', 1);
|
$field->attr('value', 1);
|
||||||
if($template->noUnpublish) {
|
if($template->noUnpublish) {
|
||||||
@@ -1426,7 +1428,7 @@ class ProcessTemplate extends Process {
|
|||||||
$field->attr('name', 'allowChangeUser');
|
$field->attr('name', 'allowChangeUser');
|
||||||
$field->label = $this->_("Allow the 'created user' to be changed on pages?");
|
$field->label = $this->_("Allow the 'created user' to be changed on pages?");
|
||||||
$field->description = $this->_("When checked, pages using this template will have an option to change the 'created by user' (for superusers only). It will also enable the \$page->createdUser or \$page->created_users_id fields to be saved via the API."); // allowChangeUser option, description
|
$field->description = $this->_("When checked, pages using this template will have an option to change the 'created by user' (for superusers only). It will also enable the \$page->createdUser or \$page->created_users_id fields to be saved via the API."); // allowChangeUser option, description
|
||||||
if($this->wire('config')->advanced) $field->notes = 'API: $template->allowChangeUser = 1; // ' . $label0;
|
if($advanced) $field->notes = 'API: $template->allowChangeUser = 1; // ' . $label0;
|
||||||
|
|
||||||
$field->attr('value', 1);
|
$field->attr('value', 1);
|
||||||
if($template->allowChangeUser) {
|
if($template->allowChangeUser) {
|
||||||
@@ -1443,7 +1445,7 @@ class ProcessTemplate extends Process {
|
|||||||
$field->attr('name', 'noMove');
|
$field->attr('name', 'noMove');
|
||||||
$field->label = $this->_("Don't allow pages to be moved?");
|
$field->label = $this->_("Don't allow pages to be moved?");
|
||||||
$field->description = $this->_("If you want to prevent pages using this template from being moved (changing parent) then check this box."); // noMove option, description
|
$field->description = $this->_("If you want to prevent pages using this template from being moved (changing parent) then check this box."); // noMove option, description
|
||||||
if($this->wire('config')->advanced) $field->notes = 'API: $template->noMove = 1; // ' . $label0;
|
if($advanced) $field->notes = 'API: $template->noMove = 1; // ' . $label0;
|
||||||
|
|
||||||
$field->attr('value', 1);
|
$field->attr('value', 1);
|
||||||
if($template->noMove) {
|
if($template->noMove) {
|
||||||
@@ -1461,7 +1463,7 @@ class ProcessTemplate extends Process {
|
|||||||
$field->attr('name', 'noLang');
|
$field->attr('name', 'noLang');
|
||||||
$field->label = $this->_("Disable multi-language support for this template?");
|
$field->label = $this->_("Disable multi-language support for this template?");
|
||||||
$field->description = $this->_("When checked, pages using this template will only use the default language.");
|
$field->description = $this->_("When checked, pages using this template will only use the default language.");
|
||||||
if($this->wire('config')->advanced) $field->notes = 'API: $template->noLang = 1; // ' . $label0;
|
if($advanced) $field->notes = 'API: $template->noLang = 1; // ' . $label0;
|
||||||
|
|
||||||
$field->attr('value', 1);
|
$field->attr('value', 1);
|
||||||
if($template->noLang) {
|
if($template->noLang) {
|
||||||
@@ -2128,10 +2130,40 @@ class ProcessTemplate extends Process {
|
|||||||
$field->attr('value', $template->noInherit ? 1 : 0);
|
$field->attr('value', $template->noInherit ? 1 : 0);
|
||||||
$fieldset->add($field);
|
$fieldset->add($field);
|
||||||
|
|
||||||
|
|
||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the "pagefileSecure" field for the "access" tab
|
||||||
|
*
|
||||||
|
* @param Template $template
|
||||||
|
* @return InputfieldRadios
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function buildEditFormAccessFiles(Template $template) {
|
||||||
|
|
||||||
|
/** @var InputfieldRadios $f */
|
||||||
|
$f = $this->modules->get('InputfieldRadios');
|
||||||
|
$f->attr('id+name', 'pagefileSecure');
|
||||||
|
$f->label = $this->_('Prevent direct access to file assets owned by pages using this template?');
|
||||||
|
$f->icon = 'download';
|
||||||
|
$f->description =
|
||||||
|
$this->_('When direct access to a file in [u]/site/assets/files/[/u] is blocked, ProcessWire can manage delivery of the file, rather than Apache.') . ' ' .
|
||||||
|
$this->_('This enables the file to be access controlled in the same manner as the page that owns it, while still using the original file URL.') . ' ' .
|
||||||
|
$this->_('Note that it takes more overhead to deliver a file this way, so only choose the “Yes always” option if you need it.');
|
||||||
|
$f->notes =
|
||||||
|
$this->_('Always test that the access control is working how you expect by attempting to access the protected file(s) in your browser.') . ' ' .
|
||||||
|
$this->_('Do this for when you expect to have access (logged-in) and when you do not (logged-out).');
|
||||||
|
$f->addOption(0, $this->_('No') . ' ' .
|
||||||
|
'[span.detail] ' . $this->_('(uses site-wide configuration instead)') . ' [/span]');
|
||||||
|
$f->addOption(1, $this->_('Yes when page is unpublished, in the trash, or not publicly accessible'));
|
||||||
|
$f->addOption(2, $this->_('Yes always, regardless of page status or access control'));
|
||||||
|
$f->val((int) $template->pagefileSecure);
|
||||||
|
if(!$template->pagefileSecure) $f->collapsed = Inputfield::collapsedYes;
|
||||||
|
|
||||||
|
return $f;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the "roles" field for "access" tab in edit form
|
* Build the "roles" field for "access" tab in edit form
|
||||||
*
|
*
|
||||||
@@ -2455,6 +2487,9 @@ class ProcessTemplate extends Process {
|
|||||||
$template->altFilename = basename($form->get('altFilename')->attr('value'), "." . $config->templateExtension);
|
$template->altFilename = basename($form->get('altFilename')->attr('value'), "." . $config->templateExtension);
|
||||||
$template->guestSearchable = (int) $form->get('guestSearchable')->attr('value');
|
$template->guestSearchable = (int) $form->get('guestSearchable')->attr('value');
|
||||||
$template->noInherit = (int) $form->get('noInherit')->attr('value') ? 1 : 0;
|
$template->noInherit = (int) $form->get('noInherit')->attr('value') ? 1 : 0;
|
||||||
|
|
||||||
|
$pagefileSecurePrev = (int) $template->pagefileSecure;
|
||||||
|
$template->pagefileSecure = (int) $input->post('pagefileSecure');
|
||||||
|
|
||||||
$pageLabelField = $form->get('pageLabelField')->attr('value');
|
$pageLabelField = $form->get('pageLabelField')->attr('value');
|
||||||
if(strpos($pageLabelField, '{') !== false && strpos($pageLabelField, '}')) {
|
if(strpos($pageLabelField, '{') !== false && strpos($pageLabelField, '}')) {
|
||||||
@@ -2666,6 +2701,23 @@ class ProcessTemplate extends Process {
|
|||||||
}
|
}
|
||||||
unset($cloneTemplateName, $_cloneTemplateName);
|
unset($cloneTemplateName, $_cloneTemplateName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// change to the pagefileSecure setting
|
||||||
|
if($pagefileSecurePrev !== $template->pagefileSecure) {
|
||||||
|
$findSelector = "templates_id=$template->id, include=all";
|
||||||
|
$qty = $this->wire()->pages->count($findSelector);
|
||||||
|
if($qty < 1000) {
|
||||||
|
$qty = $template->checkPagefileSecure();
|
||||||
|
$this->message(sprintf($this->_('Renamed %d page file path(s) for change to secure files option'), $qty), Notice::noGroup);
|
||||||
|
} else {
|
||||||
|
$this->warning(
|
||||||
|
sprintf($this->_('Your change to the secure files option will be applied as file/image fields on each of the %d affected pages are accessed.'), $qty) . ' ' .
|
||||||
|
$this->_('Note that this may take some time. To apply to all now, execute the following API code from a template file:') . ' ' .
|
||||||
|
"`\$templates->get('$template->name')->checkPagefileSecure();`",
|
||||||
|
Notice::noGroup
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!$redirectUrl) $redirectUrl = "edit?id={$template->id}";
|
if(!$redirectUrl) $redirectUrl = "edit?id={$template->id}";
|
||||||
$session->redirect($redirectUrl);
|
$session->redirect($redirectUrl);
|
||||||
|
Reference in New Issue
Block a user