1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-18 12:31:17 +02:00

15 Commits

Author SHA1 Message Date
Ryan Cramer
44fcf13ea2 Bump version to 3.0.246 2025-02-14 10:10:03 -05:00
Ryan Cramer
69270a31b0 Fix issue processwire/processwire-issues#2039 2025-02-14 10:00:01 -05:00
Ryan Cramer
f88350baa5 Fix issue processwire/processwire-issues#2040 2025-02-14 09:57:42 -05:00
Ryan Cramer
c7ba08ecb9 Update ProcessController::jsonMessage() method so that it supports array argument, primarily for internal debugging purposes 2025-02-07 14:21:58 -05:00
Ryan Cramer
59ae7f4c7f Fix issue processwire/processwire-issues#2023 2025-02-06 11:04:21 -05:00
Ryan Cramer
0708865081 Update min PHP version in composer.json file. processwire/processwire-issues#2024 2025-02-06 10:50:17 -05:00
Ryan Cramer
6ae349b4ec Fix issue processwire/processwire-issues#2024 2025-02-06 10:35:22 -05:00
Ryan Cramer
30b34c70b3 Fix issue processwire/processwire-issues#2025 2025-02-06 10:31:22 -05:00
Ryan Cramer
e6dbc3e8eb Fix issue processwire/processwire-issues#2026 2025-02-06 10:06:10 -05:00
Ryan Cramer
a959afc422 Fix issue processwire/processwire-issues#2029 2025-02-06 09:04:08 -05:00
Ryan Cramer
4a9b904b77 Fix issue processwire/processwire-issues#2030 2025-02-06 08:48:36 -05:00
Ryan Cramer
8b5d96f1b6 Fix issue processwire/processwire-issues#2035 plus some related additions to help identify and fix pages that might incorrectly have trash status 2025-02-05 14:45:05 -05:00
Ryan Cramer
eddd6cb8ad Bump version to 3.0.245 2025-01-29 15:22:03 -05:00
Ryan Cramer
e1e938591d Updates to ProcessPageList to make some parts more hookable 2025-01-29 10:02:06 -05:00
Ryan Cramer
1805ad0a59 Improvements to FieldtypePage set of string value(s) to pages, so that it recognizes title and name for setting purposes. 2025-01-29 09:55:22 -05:00
22 changed files with 179 additions and 80 deletions

View File

@@ -14,7 +14,7 @@
}
],
"require": {
"php": ">=5.5",
"php": ">=7.1",
"ext-gd": "*"
},
"autoload": {

View File

@@ -47,7 +47,7 @@ class Installer {
* Minimum required PHP version to install ProcessWire
*
*/
const MIN_REQUIRED_PHP_VERSION = '5.3.8';
const MIN_REQUIRED_PHP_VERSION = '7.1.0';
/**
* Test mode for installer development, non destructive
@@ -1835,10 +1835,6 @@ class Installer {
if($value === null && empty($sanitizer)) return null;
if(version_compare(PHP_VERSION, "5.4.0", "<") && function_exists('get_magic_quotes_gpc')) {
if(get_magic_quotes_gpc()) $value = stripslashes($value);
}
switch($sanitizer) {
case 'intSigned':
$value = (int) $value;

View File

@@ -465,7 +465,7 @@ abstract class DatabaseQuery extends WireData {
if(is_array($value)) {
$curValue = array_merge($curValue, $value);
} else {
$curValue[] = trim($value, ", ");
$curValue[] = trim("$value", ", ");
}
$this->set($method, $curValue);
@@ -756,4 +756,3 @@ abstract class DatabaseQuery extends WireData {
}
}

View File

@@ -815,6 +815,11 @@ class PagesEditor extends Wire {
if($page->templatePrevious) $this->pages->templateChanged($page);
if(in_array('status', $changes)) $this->pages->statusChanged($page);
}
if($triggerAddedPage && $page->rootParent()->id === $this->wire()->config->trashPageID) {
// new page created directly in trash, not a great way to start but that's how it is
$this->savePageStatus($page, Page::statusTrash);
}
$this->pages->debugLog('save', $page, true);
@@ -1094,29 +1099,34 @@ class PagesEditor extends Wire {
$database = $this->wire()->database;
$rowCount = 0;
$multi = is_array($pageID) || $pageID instanceof PageArray;
$page = $pageID instanceof Page ? $pageID : null;
$status = (int) $status;
if($status < 0 || $status > Page::statusMax) {
throw new WireException("status must be between 0 and " . Page::statusMax);
}
$sql = "UPDATE pages SET status=";
$sqlUpdate = "UPDATE pages SET status=";
if($remove === 2) {
// overwrite status (internal/undocumented)
$sql .= "status=$status";
$sqlUpdate .= "status=$status";
if($page instanceof Page) $page->status = $status;
} else if($remove) {
// remove status
$sql .= "status & ~$status";
$sqlUpdate .= "status & ~$status";
if($page instanceof Page) $page->removeStatus($status);
} else {
// add status
$sql .= "status|$status";
$sqlUpdate .= "status|$status";
if($page instanceof Page) $page->addStatus($status);
}
if($multi && $recursive) {
// multiple page IDs combined with recursive option, must be handled individually
foreach($pageID as $id) {
$rowCount += $this->savePageStatus((int) "$id", $status, $recursive, $remove);
$id = $id instanceof Page ? $id : (int) "$id";
$rowCount += $this->savePageStatus($id, $status, $recursive, $remove);
}
// exit early in this case
return $rowCount;
@@ -1128,15 +1138,17 @@ class PagesEditor extends Wire {
$id = (int) "$id";
if($id > 0) $ids[$id] = $id;
}
if(!count($ids)) $ids[] = 0;
$query = $database->prepare("$sql WHERE id IN(" . implode(',', $ids) . ")");
$database->execute($query);
return $query->rowCount();
if(count($ids)) {
$query = $database->prepare("$sqlUpdate WHERE id IN(" . implode(',', $ids) . ")");
$database->execute($query);
$rowCount = $query->rowCount();
}
return $rowCount;
} else {
// single page ID or Page object
$pageID = (int) "$pageID";
$query = $database->prepare("$sql WHERE id=:page_id");
$query = $database->prepare("$sqlUpdate WHERE id=:page_id");
$query->bindValue(":page_id", $pageID, \PDO::PARAM_INT);
$database->execute($query);
$rowCount = $query->rowCount();
@@ -1146,12 +1158,13 @@ class PagesEditor extends Wire {
// recursive mode assumed from this point forward
$parentIDs = array($pageID);
$ids = [];
do {
$parentID = array_shift($parentIDs);
// update all children to have the same status
$query = $database->prepare("$sql WHERE parent_id=:parent_id");
$query = $database->prepare("$sqlUpdate WHERE parent_id=:parent_id");
$query->bindValue(":parent_id", $parentID, \PDO::PARAM_INT);
$database->execute($query);
$rowCount += $query->rowCount();
@@ -1171,13 +1184,19 @@ class PagesEditor extends Wire {
/** @noinspection PhpAssignmentInConditionInspection */
while($row = $query->fetch(\PDO::FETCH_ASSOC)) {
$parentIDs[] = (int) $row['id'];
$id = (int) $row['id'];
$parentIDs[$id] = $id;
$ids[$id] = $id;
}
$query->closeCursor();
} while(count($parentIDs));
if(count($ids)) {
$rowCount += $this->savePageStatus($ids, $status, false, $remove);
}
return $rowCount;
}

View File

@@ -126,6 +126,7 @@ class PagesTrash extends Wire {
if($info['restorable']) {
// we detected original parent
if($this->lastAction !== "restore:$page") $this->pages->restoreReady($page);
$this->pages->editor()->savePageStatus($page->id, Page::statusTrash, true, true);
} else if(!$page->parent->isTrash()) {
// page has had new parent already set

View File

@@ -251,6 +251,8 @@ abstract class Process extends WireData implements Module {
*
*/
public function ___breadcrumb($href, $label) {
if(is_array($label)) return $this;
$label = (string) $label;
$pos = strpos($label, '/');
if($pos !== false && strpos($href, '/') === false) {
// arguments got reversed, we'll work with it anyway...

View File

@@ -486,18 +486,22 @@ class ProcessController extends Wire {
/**
* Generate a message in JSON format, for use with AJAX output
*
* @param string $msg
* @param bool $error
* @param bool $allowMarkup
* @param string|array $msg Message string or in 3.0.246+ also accepts an array of extra data
* When using an array, please include a 'message' index with text about the error or non-error.
* @param bool $error Is this in error message? Default is true, or specify false if not.
* @param bool $allowMarkup Allow markup in message? Applies only to $msg string or 'message' index of array (default=false)
* @return string JSON encoded string
*
*/
public function jsonMessage($msg, $error = false, $allowMarkup = false) {
if(!$allowMarkup) $msg = $this->wire()->sanitizer->entities($msg);
return json_encode(array(
'error' => (bool) $error,
'message' => (string) $msg
));
$a = array('error' => (bool) $error, 'message' => '');
if(is_array($msg)) {
$a = array_merge($a, $msg);
} else {
$a['message'] = (string) $msg;
}
if(!$allowMarkup) $a['message'] = $this->wire()->sanitizer->entities($a['message']);
return json_encode($a);
}
/**

View File

@@ -80,7 +80,7 @@ class ProcessWire extends Wire {
* Reversion revision number
*
*/
const versionRevision = 244;
const versionRevision = 246;
/**
* Version suffix string (when applicable)

View File

@@ -72,6 +72,11 @@ class WireTempDir extends Wire {
if(!is_null($this->tempDirRoot)) throw new WireException("Temp dir has already been created");
if(empty($name)) $name = $this->createName();
if(is_object($name)) $name = wireClassName($name, false);
if($basePath && !$this->wire()->files->allowPath($basePath, true)) {
$this->log("Given base path $basePath is not within ProcessWire assets so has been replaced");
$basePath = '';
}
$basePath = $this->classRootPath(true, $basePath);
$this->classRoot = $basePath;

View File

@@ -810,6 +810,7 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule
}
$pages = $this->wire()->pages;
$inputfield = null;
// value is an int or array of int|string|Page, load to pages, add to $pageArray
if(!is_array($value)) $value = array($value);
@@ -818,17 +819,24 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule
// Page object
if($v->id == $page->id) continue;
$pg = $v;
} else if(is_int($v)) {
} else if(is_int($v) || ctype_digit("$v")) {
// integer page ID
if($v == $page->id) continue;
$pg = $pages->get($v);
} else if(is_string($v)) {
// path or selector string
if(ctype_digit($v)) {
$v = (int) $v;
if($v == $page->id) continue;
if(strpos($v, '/') === 0 && $this->wire()->sanitizer->pagePathName($v) === $v) {
$pg = $pages->get($v);
} else if(Selectors::stringHasSelector($v)) {
$pg = $pages->get($v);
} else {
$pg = null;
}
if(!$pg || !$pg->id) {
if(!$inputfield) $inputfield = $this->getInputfield($page, $field);
$pgs = $inputfield->getSelectablePages($page, 'name|title=' . $this->wire()->sanitizer->selectorValue($v));
if(count($pgs)) $pg = $pgs->first();
}
$pg = $pages->get($v);
} else {
// unrecognized type: can't make a page object from it
continue;

View File

@@ -175,16 +175,17 @@ class RepeaterPageArray extends PageArray {
if(is_null($page)) {
// no ready item available, get a new one
$page = $fieldtype->getBlankRepeaterPage($this->forPage, $this->field);
$page = $fieldtype->getBlankRepeaterPage($this->forPage, $this->field);
$page->sort = $this->count();
$this->add($page);
} else {
$page->sort = $this->count();
$this->trackChange('add');
}
$page->of(false);
$page->removeStatus(Page::statusUnpublished);
$page->removeStatus(Page::statusHidden);
$page->sort = $this->count();
if($of) $this->forPage->of(true);

View File

@@ -30,7 +30,7 @@
* @method string renderList($value)
* @method string renderUpload($value)
* @method void fileAdded(Pagefile $pagefile)
* @method array extractMetadata(Pagefile $pagefile, array $metadata = array())
* @method array extractMetadata(Pagefile $pagefile, array $metadata = []) Given a Pagefile return array of meta data pulled from it
* @method Pagefile|null processInputAddFile($filename)
* @method void processInputDeleteFile(Pagefile $pagefile)
* @method bool processInputFile(WireInputData $input, Pagefile $pagefile, $n)
@@ -929,7 +929,7 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
* @return array Associative array of meta data (i.e. description and tags)
*
*/
protected function ___extractMetadata(Pagefile $pagefile, array $metadata = array()) {
protected function ___extractMetadata(Pagefile $pagefile, array $metadata = []) {
$languages = $this->wire()->languages;
@@ -1507,7 +1507,11 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
if($item) {
$hasPage = $this->hasPage;
if($hasPage && wireInstanceOf($hasPage, 'RepeaterPage')) {
if(strpos($this->name, '_repeater') === false) {
$process = $this->wire()->process;
if($process instanceof WirePageEditor && $process->getPage()->id === $hasPage->id) {
// repeater page being edited directly or in front-end modal
// so no '_repeater' suffix is necessary here
} else if(strpos($this->name, '_repeater') === false) {
// ensures that custom fields are properly namespaced within repeater
// though note that this prevents it from working when editing a repeater
// page directly, independently of its forPage

View File

@@ -5,7 +5,7 @@
*
* Delegates the actual input control to a user-defined Inputfield derived from InputfieldSelect
*
* @method PageArray|null getSelectablePages(Page $page)
* @method PageArray|null getSelectablePages(Page $page, $filterSelector = '')
* @method PageArray findPagesCode(Page $page)
*
* Can be accessed from $this or from $field:
@@ -61,7 +61,8 @@ class InputfieldPage extends Inputfield implements ConfigurableModule {
'InputfieldCheckboxes',
'InputfieldRadios',
'InputfieldAsmSelect',
'InputfieldPageListSelect',
'InputfieldPageListSelect',
'InputfieldPageListSelectMultiple',
'InputfieldPageAutocomplete',
'InputfieldTextTags',
);
@@ -349,10 +350,11 @@ class InputfieldPage extends Inputfield implements ConfigurableModule {
* Return PageArray of selectable pages for this input
*
* @param Page $page The Page being edited
* @param string $filterSelector Additional selector to filter selectable pages (3.0.245+)
* @return PageArray|null
*
*/
public function ___getSelectablePages(Page $page) {
public function ___getSelectablePages(Page $page, $filterSelector = '') {
$pages = $this->wire()->pages;
$lockedModes = array(Inputfield::collapsedNoLocked, Inputfield::collapsedYesLocked, Inputfield::collapsedBlankLocked);
@@ -386,25 +388,30 @@ class InputfieldPage extends Inputfield implements ConfigurableModule {
$selector = self::populateFindPagesSelector($page, $findPagesSelector, $instance);
if($templateIDs) $selector = trim("$selector, templates_id=$templateIDs", ", ");
if($this->parent_id) $selector = trim("$selector, parent_id=$this->parent_id", ", ");
if($filterSelector) $selector .= ", $filterSelector";
$children = $pages->find($selector);
} else if($this->findPagesCode) {
// php statement that returns a PageArray or a Page (to represent a parent)
$children = $this->findPagesCode($page);
if($children instanceof Page) $children = $children->children(); // @teppokoivula
if($children instanceof Page) $children = $children->children($filterSelector); // @teppokoivula
} else if($this->parent_id) {
$parent = $pages->get($this->parent_id);
if($parent) {
if($templateIDs) {
$children = $parent->children("templates_id=$templateIDs, check_access=0, status<$statusUnder");
$selector = "templates_id=$templateIDs, check_access=0, status<$statusUnder";
} else {
$children = $parent->children("check_access=0, status<$statusUnder");
$selector = "check_access=0, status<$statusUnder";
}
if($filterSelector) $selector .= ", $filterSelector";
$children = $parent->children($selector);
}
} else if($templateIDs) {
$children = $pages->find("templates_id=$templateIDs, check_access=0, status<$statusUnder");
$selector = "templates_id=$templateIDs, check_access=0, status<$statusUnder";
if($filterSelector) $selector .= ", $filterSelector";
$children = $pages->find($selector);
} else {
$children = $pages->newPageArray();

View File

@@ -2153,19 +2153,12 @@ class ProcessField extends Process implements ConfigurableModule {
$form->attr('id', 'advanced');
$form->attr('class', 'WireTab');
$form->attr('title', $this->_x('Advanced', 'tab'));
$tags = array();
$textTools = $this->wire()->sanitizer->getTextTools();
foreach($this->wire()->fields->getTags() as $tagKey => $tagValue) {
$tagKey = $textTools->strtolower($tagKey);
$tags[$tagKey] = $tagValue;
}
ksort($tags);
/** @var InputfieldTextTags $field */
$field = $this->wire()->modules->getInstall('InputfieldTextTags');
$field->attr('name', 'tags');
$field->allowUserTags = true;
if(count($tags)) $field->setTagsList($tags);
$field->setTagsList($this->wire()->fields->getTags());
$field->attr('value', $this->field->getTags(true));
$field->icon = 'tags';
$field->label = $this->labels['tags'];
@@ -2574,7 +2567,7 @@ class ProcessField extends Process implements ConfigurableModule {
}
if($name === 'tags') {
$value = $sanitizer->getTextTools()->strtolower($sanitizer->words($value));
$value = $sanitizer->words($value);
}
$this->field->set($name, $value);

View File

@@ -1046,11 +1046,17 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod
if($this->page->addable() || $this->page->numChildren) $form->append($this->buildFormChildren());
if(!$this->page->template->noSettings && $this->useSettings) $form->append($this->buildFormSettings());
if($this->isTrash && !$this->isPost) {
$this->message($this->_("This page is in the Trash"));
$tabRestore = $this->buildFormRestore();
if($tabRestore) $form->append($tabRestore);
if($this->page->rootParent()->id != $this->config->trashPageID) {
$this->warning($this->_('This page incorrectly has trash status, please “save” to fix it'));
} else {
$this->message($this->_('This page is in the Trash'));
$tabRestore = $this->buildFormRestore();
if($tabRestore) $form->append($tabRestore);
}
}
$tabDelete = $this->buildFormDelete();
if($tabDelete->children()->count()) $form->append($tabDelete);
if($this->page->viewable() && !$this->requestModal) $this->buildFormView($this->getViewUrl());
@@ -2565,6 +2571,11 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod
}
}
if(($value & Page::statusTrash) && $this->page->rootParent()->id != $this->config->trashPageID) {
$value = $value & ~Page::statusTrash;
$this->message($this->_('Removed trash status'));
}
$this->page->status = $value;
return true;

View File

@@ -228,6 +228,7 @@ $(document).ready(function() {
function hideItem($item) {
var $actions = $item.find('.PageListActions');
if($item.hasClass('PageListItemOpen') && $actions.hasClass('PageListActionsKeepOpen')) return;
$item.removeClass('PageListItemHover');
if($actions.is(":visible")) { // || $hoveredItem.hasClass('PageListItemOpen')) {
$actions.animate({opacity: 0}, options.hoverActionFade, function () {
@@ -564,6 +565,9 @@ $(document).ready(function() {
} else {
$loading.fadeOut('fast');
}
data.list = $target;
data.item = $target.prev('.PageListItem');
if(replace) {
$children.show();

File diff suppressed because one or more lines are too long

View File

@@ -27,6 +27,7 @@
* @method string ajaxAction($action)
* @method PageArray find($selectorString, Page $page)
* @method array getHidePageIDs(array $a = array())
* @method string getSelector(Page $page, array $options)
*
* @todo Option to configure whether "Pub" action should appear for non-superusers
*
@@ -381,16 +382,9 @@ class ProcessPageList extends Process implements ConfigurableModule {
if(is_null($limit)) $limit = $this->limit;
if(is_null($start)) $start = $this->start;
if($limit) {
$selector = "start=$start, limit=$limit, status<" . Page::statusMax;
if($this->useTrash && !$superuser) {
$trashID = $this->wire()->config->trashPageID;
if($page->id == $trashID && $user->hasPermission('page-edit') && $page->listable()) {
$selector .= ", check_access=0";
}
}
$selector = $this->getSelector($page, [ 'start' => $start, 'limit' => $limit ]);
if(strlen($selector)) {
$children = $this->find($selector, $page);
} else {
$children = $this->wire()->pages->newPageArray();
@@ -409,6 +403,46 @@ class ProcessPageList extends Process implements ConfigurableModule {
return $renderer;
}
/**
* Get selector string to use in finding children
*
* @param Page $page
* @param array $options
* @return string
* @since 3.0.245
*
*/
public function ___getSelector(Page $page, array $options) {
$defaults = [
'start' => 0,
'limit' => 50,
];
$options = array_merge($defaults, $options);
$user = $this->wire()->user;
$start = (int) $options['start'];
$limit = (int) $options['limit'];
$selector = "start=$start, limit=$limit, status<" . Page::statusMax;
if($this->useTrash && !$user->isSuperuser()) {
$trashID = $this->wire()->config->trashPageID;
if($page->id == $trashID && $user->hasPermission('page-edit') && $page->listable()) {
$selector .= ", check_access=0";
}
}
if($page->id === $this->wire()->config->trashPageID && !preg_match('/\bsort=/', $selector)) {
$sortfield = $page->sortfield();
if(!$sortfield || $sortfield === 'sort') {
$selector = trim("$selector, sort=-modified", ',');
}
}
return $selector;
}
/**
* Set the page label field
*
@@ -444,6 +478,17 @@ class ProcessPageList extends Process implements ConfigurableModule {
return $pageLabelField;
}
/**
* Get parent page that current request is for
*
* @return Page|null
* @since 3.0.245
*
*/
public function getPage() {
return $this->page;
}
/**
* Process an AJAX action and return JSON string
*
@@ -491,12 +536,6 @@ class ProcessPageList extends Process implements ConfigurableModule {
*
*/
public function ___find($selectorString, Page $page) {
if($page->id === $this->wire()->config->trashPageID && !preg_match('/\bsort=/', $selectorString)) {
$sortfield = $page->sortfield();
if(!$sortfield || $sortfield === 'sort') {
$selectorString = trim("$selectorString,sort=-modified", ',');
}
}
return $page->children($selectorString);
}

View File

@@ -132,6 +132,10 @@ class ProcessPageListRenderJSON extends ProcessPageListRender {
if($page->hasStatus(Page::statusLocked)) $icons[] = 'lock';
if($page->hasStatus(Page::statusDraft)) $icons[] = 'paperclip';
if($page->hasStatus(Page::statusFlagged)) $icons[] = 'exclamation-triangle';
if($page->hasStatus(Page::statusTrash) && !$page->rootParent()->isTrash()) {
$icons[] = 'trash';
$icons[] = 'exclamation-triangle';
}
$numChildren = $this->numChildren($page, 1);
$numTotal = strpos($this->qtyType, 'total') !== false ? $page->numDescendants : $numChildren;

View File

@@ -440,7 +440,7 @@ class ProcessPageSearch extends Process implements ConfigurableModule {
} // foreach input
if(strpos($selectors['for'], 'limit=') && $limit === $this->resultLimit) {
if(isset($selectors['for']) && strpos($selectors['for'], 'limit=') && $limit === $this->resultLimit) {
if(preg_match('/\blimit=(\d+),?/', $selectors['for'], $matches)) {
$limit = (int) $matches[1];
$selectors['for'] = str_replace($matches[0], '', $selectors['for']);

View File

@@ -2065,8 +2065,10 @@ function InputfieldDependencies($target) {
var values = [];
// For repeaters PR #255
if(field.indexOf('forpage.') === 0) {
field = field.replace('forpage.', '').replace(/\_repeater\d+/g, '');
if(field.indexOf('forpage_repeater') === 0) {
field = field.replace(/forpage_repeater\d+\./g, '');
} else if(field.indexOf('forpage.') === 0) {
field = field.replace('forpage.', '').replace(/_repeater\d+/g, '');
}
// detect OR selector in field

File diff suppressed because one or more lines are too long