You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1538 lines
50 KiB
Plaintext
1538 lines
50 KiB
Plaintext
<?php
|
|
|
|
/**
|
|
* Page Lister Pro: Process
|
|
* __ _ __
|
|
* / / (_)____/ /____ _____
|
|
* / / / / ___/ __/ _ \/ ___/
|
|
* / /___/ (__ ) /_/ __/ /
|
|
* /_____/_/____/\__/\___/_/ PRO
|
|
*
|
|
* Provides an alternative listing view for pages using specific templates (Professional Version)
|
|
*
|
|
* This is a commercial module, please do not distribute.
|
|
*
|
|
* ListerPro for ProcessWire
|
|
* Copyright 2017 by Ryan Cramer
|
|
* https://processwire.com/ListerPro/
|
|
*
|
|
* PWLPID17
|
|
*
|
|
* @todo: deleted field that is present in editColumns property can cause exceptions (https://processwire.com/talk/topic/15935-add-hook-method-on-fields-delete-and-name-change/)
|
|
* @todo: add support for user-specific bookmarks (https://processwire.com/talk/topic/13117-request-add-permission-to-createedit-bookmarks/)
|
|
* @todo: allow for subfields of "modified user" and "created user"
|
|
* @todo: include=unpublished overrides status "does not have" unpublished
|
|
* @todo: allow for option of locking the field select boxes
|
|
* @todo: limitFields option is limiting all column fields, even config ones.
|
|
* @todo: option to merge config and bookmarks per here https://processwire.com/talk/topic/10944-filters-reset-in-bookmarks/
|
|
*
|
|
* @property array $editColumns Field IDs that are editable in the Lister
|
|
* @property array $editFieldtypes Fieldtype classnames allowed for editColumns
|
|
* @property int $editOption See editOption constants
|
|
* @property bool $editPreload Whether or not to preload editors in rendered table
|
|
* @property int|bool $useColumnLabels Whether or not to use column labels (vs. names)
|
|
* @property array $settings Where all Listers config data is stored
|
|
* @property array $allowActions Action class names that are allowed for this Lister
|
|
* @property array $limitFields Limit selectable fields to only those present here (names)
|
|
* @property array $toggles Toggles from ListerPro config $settings, specific to this Lister
|
|
* @property array $InputfieldSelectorSettings Settings to pass along to InputfieldSelector
|
|
* @property string $licenseKey
|
|
*
|
|
*
|
|
*/
|
|
|
|
class ProcessPageListerPro extends ProcessPageLister implements ConfigurableModule, WirePageEditor {
|
|
|
|
const editOptionNone = 0;
|
|
const editOptionSome = 1;
|
|
const editOptionAll = 2;
|
|
|
|
/**
|
|
* @var ListerProActions|null
|
|
*
|
|
*/
|
|
protected $actions = null;
|
|
|
|
/**
|
|
* For editable columns and WirePageEditor interface getPage() method
|
|
*
|
|
* @var null|Page
|
|
*
|
|
*/
|
|
protected $editorPage = null;
|
|
|
|
|
|
/**
|
|
* Parent class name either \\ProcessWire\\ProcessPageLister or just ProcessPageLister
|
|
*
|
|
* @var string
|
|
*
|
|
*/
|
|
protected $parentClass;
|
|
|
|
/**
|
|
* Construct ListerPro
|
|
*
|
|
*/
|
|
public function __construct() {
|
|
|
|
$url = $this->wire('config')->urls->ProcessPageLister;
|
|
$this->wire('config')->styles->add($url . "ProcessPageLister.css");
|
|
$this->wire('config')->scripts->add($url . "ProcessPageLister.js");
|
|
$this->parentClass = "\\ProcessWire\\ProcessPageLister";
|
|
if(!class_exists($this->parentClass)) $this->parentClass = "ProcessPageLister";
|
|
|
|
parent::__construct();
|
|
|
|
// the following will be overridden per ListerPro config
|
|
$this->set('useColumnLabels', 0);
|
|
|
|
// where all listers config data is stored
|
|
$this->set('settings', array());
|
|
|
|
// action class names that are allowed for this Lister
|
|
$this->set('allowActions', array());
|
|
|
|
// IDs of fields that are allowed to be edited in the Lister
|
|
$this->set('editColumns', array());
|
|
|
|
// If true, then all editable columns can be used (bypassing editColumns setting)
|
|
$this->set('editOption', self::editOptionNone);
|
|
|
|
// List of all Fieldtypes modules allowed for editable columns (empty=allow all)
|
|
$this->set('editFieldtypes', array());
|
|
|
|
// If true, editors will always be rendered
|
|
$this->set('editPreload', false);
|
|
|
|
$this->set('limitFields', array());
|
|
$this->set('licenseKey', '');
|
|
$this->set('initSelector', '');
|
|
$this->set('toggles', array());
|
|
|
|
$this->set('imageStyle', 0); // 0=image only, 1=detailed
|
|
|
|
$dirname = dirname(__FILE__) . '/';
|
|
require_once($dirname . '/ListerProConfig.php');
|
|
require_once($dirname . '/ListerProActions.php');
|
|
}
|
|
|
|
/**
|
|
* Initalize lister variables
|
|
*
|
|
*/
|
|
public function init() {
|
|
|
|
$name = $this->page->name;
|
|
|
|
$settings = $this->settings;
|
|
|
|
if($settings && isset($settings[$name])) {
|
|
$settings = $settings[$name]; // convert to localized settings specific to this lister
|
|
//$this->parent = empty($settings['parent']) || ((int) $settings['parent']) === 1 ? new NullPage() : $this->pages->get((int) $settings['parent']);
|
|
$this->parent = empty($settings['parent']) ? new NullPage() : $this->pages->get((int) $settings['parent']);
|
|
if(isset($settings['defaultSort'])) $this->defaultSort = $settings['defaultSort'];
|
|
if(isset($settings['columns'])) $this->columns = $settings['columns'];
|
|
if(isset($settings['initSelector'])) $this->initSelector = $settings['initSelector'];
|
|
if(isset($settings['defaultSelector'])) $this->defaultSelector = $settings['defaultSelector'];
|
|
if(isset($settings['delimiters'])) $this->delimiters = $settings['delimiters'];
|
|
if(isset($settings['allowActions'])) $this->allowActions = $settings['allowActions'];
|
|
if(isset($settings['limitFields'])) $this->limitFields = $settings['limitFields'];
|
|
if(isset($settings['imageWidth'])) $this->imageWidth = (int) $settings['imageWidth'];
|
|
if(isset($settings['imageHeight'])) $this->imageHeight = (int) $settings['imageHeight'];
|
|
if(isset($settings['imageFirst'])) $this->imageFirst = $settings['imageFirst'];
|
|
if(isset($settings['imageStyle'])) $this->imageStyle = $settings['imageStyle'];
|
|
if(isset($settings['viewMode'])) $this->viewMode = (int) $settings['viewMode'];
|
|
if(isset($settings['editMode'])) $this->editMode = (int) $settings['editMode'];
|
|
if(isset($settings['toggles'])) $this->toggles = $settings['toggles'];
|
|
if(isset($settings['useColumnLabels'])) $this->useColumnLabels = (int) $settings['useColumnLabels'];
|
|
if(isset($settings['InputfieldSelectorSettings'])) $this->InputfieldSelectorSettings = $settings['InputfieldSelectorSettings'];
|
|
if(isset($settings['editOption'])) $this->editOption = $settings['editOption'];
|
|
if(isset($settings['editColumns'])) $this->editColumns = $settings['editColumns'];
|
|
if(isset($settings['editPreload'])) $this->editPreload = $settings['editPreload'];
|
|
|
|
} else if($this->input->urlSegment1 == 'config') {
|
|
// defaults for unconfigured lister
|
|
if($name != 'lister') $this->initSelector = 'template=';
|
|
$this->columns = $this->defaultColumns;
|
|
$this->parent = new NullPage();
|
|
|
|
} else {
|
|
// send them to configure this lister
|
|
// $this->session->redirect($this->page->url . 'config/');
|
|
}
|
|
|
|
if($this->isValid()) $this->actions = new ListerProActions($this);
|
|
|
|
$this->config->js($this->className(), array(
|
|
'closeLabel' => $this->_('Close'),
|
|
'openNewLabel' => $this->_('Open in new window'),
|
|
));
|
|
|
|
if(!$this->wire('config')->ajax) {
|
|
foreach($this->editColumns as $fieldID) {
|
|
if($fieldID == 'name') {
|
|
$inputfield = $this->wire('modules')->get('InputfieldPageName');
|
|
} else {
|
|
$field = $this->wire('fields')->get($fieldID);
|
|
if(!$field) continue;
|
|
// load Inputfields now to pre-load of any needed css/js files
|
|
$inputfield = $field->getInputfield(new NullPage());
|
|
}
|
|
$inputfield->renderReady();
|
|
$inputfield->render();
|
|
}
|
|
} else if(isset($_SERVER['HTTP_X_FIELDNAME'])) {
|
|
// ajax file upload gets sent directly to ProcessPageEdit
|
|
$this->processAjaxUpload();
|
|
}
|
|
|
|
parent::init();
|
|
}
|
|
|
|
protected function processAjaxUpload() {
|
|
if(!preg_match('/^(.+)_LPID(\d+)$/', $_SERVER['HTTP_X_FIELDNAME'], $matches)) return;
|
|
if(!$this->wire('user')->hasPermission('page-edit')) return;
|
|
$fieldName = $this->wire('sanitizer')->fieldName($matches[1]);
|
|
$_SERVER['HTTP_X_FIELDNAME'] = $fieldName;
|
|
$pageID = (int) $matches[2];
|
|
$_POST['id'] = $pageID;
|
|
$this->wire('input')->post->id = $pageID;
|
|
$pageEdit = $this->wire('modules')->get('ProcessPageEdit');
|
|
ob_start();
|
|
$pageEdit->execute();
|
|
$out = ob_get_contents();
|
|
ob_end_clean();
|
|
// adjust returned markup for our LPID namespace
|
|
$out = str_replace("_{$fieldName}_", "_{$fieldName}_LPID{$pageID}_", $out);
|
|
echo $out;
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* getModuleInfo interface for required permission
|
|
*
|
|
* @param array $data
|
|
* @return bool
|
|
*
|
|
*/
|
|
public static function hasListerPermission(array $data) {
|
|
|
|
/** @var Page $page */
|
|
$page = $data['page'];
|
|
/** @var User $user */
|
|
$user = $data['user'];
|
|
//$info = $data['info'];
|
|
$wire = $data['wire'];
|
|
|
|
$permission = $wire->permissions->get("page-lister-$page->name");
|
|
if(!$permission->id) $permission = 'page-lister';
|
|
|
|
return $user->hasPermission($permission);
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the InputfieldSelector instance for this Lister
|
|
*
|
|
* @return InputfieldSelector
|
|
*
|
|
*/
|
|
public function getInputfieldSelector() {
|
|
$s = parent::getInputfieldSelector();
|
|
$s->allowSubfieldGroups = true; // we only support in ListerPro
|
|
$s->allowSubselectors = true; // we only support in ListerPro
|
|
if($this->allowSystem) {
|
|
$s->allowSystemCustomFields = true;
|
|
$s->allowSystemTemplates = true;
|
|
}
|
|
|
|
if($this->InputfieldSelectorSettings) {
|
|
// populate user settings to InputfieldSelector
|
|
foreach(explode("\n", $this->InputfieldSelectorSettings) as $line) {
|
|
$line = trim($line);
|
|
$pos = strpos($line, '=');
|
|
if(!$pos) continue;
|
|
$key = substr($line, 0, $pos);
|
|
$value = substr($line, $pos+1);
|
|
$s->$key = $value;
|
|
|
|
// we will re-use 'exclude' for 'disallowColumns' Lister config option
|
|
if($key == 'exclude') {
|
|
$this->disallowColumns = array_merge($this->disallowColumns, explode(',', str_replace(' ', '', $value)));
|
|
}
|
|
}
|
|
}
|
|
|
|
return $s;
|
|
}
|
|
|
|
public function ___execute() {
|
|
|
|
/*
|
|
// @todo section hook should be enabled only if sticky-header.js is loaded
|
|
if($this->get('responsiveTable') === null) {
|
|
$this->addHookBefore('MarkupAdminDataTable::render', function($event) {
|
|
$table = $event->object;
|
|
$table->setResponsive(false);
|
|
});
|
|
} else {
|
|
$this->set('responsiveTable', false);
|
|
}
|
|
// @todo end
|
|
*/
|
|
|
|
$out = parent::___execute();
|
|
$config = $this->wire('config');
|
|
|
|
if(!$config->ajax) {
|
|
if(!method_exists($this->parentClass, 'prepareExternalAssets')) {
|
|
$this->prepareExternalAssets();
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Find and render the results (ajax)
|
|
*
|
|
* This is only called if the request comes from ajax
|
|
*
|
|
* @param string|null $selector
|
|
* @return string
|
|
*
|
|
*/
|
|
protected function ___renderResults($selector = null) {
|
|
|
|
$out = parent::___renderResults($selector);
|
|
|
|
if($this->editOption != self::editOptionNone) {
|
|
$confirmText = $this->_('There are unsaved changes. If you proceed, you will lose these changes:');
|
|
$formClass =
|
|
'Inputfield InputfieldWrapper InputfieldForm InputfieldFormConfirm ' .
|
|
//'InputfieldFormNoWidths InputfieldFormNoHeights ' .
|
|
'InputfieldFormNoDependencies InputfieldAllowAjaxUpload';
|
|
|
|
if($this->editPreload) $formClass .= " ListerEditPreload";
|
|
|
|
$out =
|
|
"<form id='table_editable' action='./' method='post' class='$formClass' data-confirm='$confirmText'>" .
|
|
//"<div class='Inputfields'>$out</div>" .
|
|
$out .
|
|
$this->wire('session')->CSRF->renderInput() .
|
|
"</form>";
|
|
|
|
}
|
|
|
|
if($this->wire('config')->ajax) {
|
|
if(strpos($out, "<div id='ProcessListerScript'>") === false) {
|
|
$out .= $this->renderExternalAssets();
|
|
}
|
|
} else if(!method_exists($this->parentClass, 'prepareExternalAssets')) {
|
|
$this->prepareExternalAssets();
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Prepare the session values for external assets, to be called during non-ajax request only
|
|
*
|
|
* This method can be removed once this version of ListerPro is always used with PW 2.6.14+
|
|
* as the method has been moved to the core ProcessPageLister.module file
|
|
*
|
|
*/
|
|
public function prepareExternalAssets() {
|
|
|
|
$config = $this->wire('config');
|
|
|
|
/*
|
|
// @todo make these available as extras that can be enabled (or not) from config tab
|
|
$url = $config->urls->ProcessPageListerPro;
|
|
$config->scripts->add($url . 'extras/multi-row-select.js');
|
|
$config->scripts->add($url . 'extras/sticky-header.js');
|
|
$config->styles->add($url . 'extras/sticky-header.css');
|
|
*/
|
|
|
|
if(method_exists($this->parentClass, "prepareExternalAssets")) {
|
|
parent::prepareExternalAssets();
|
|
return;
|
|
}
|
|
|
|
$loadedFiles = array();
|
|
$loadedJSConfig = array();
|
|
$regex = '!(Inputfield|Fieldtype|Language|Process|Jquery|Markup)!';
|
|
foreach($config->scripts as $file) {
|
|
if(!preg_match($regex, $file)) continue;
|
|
$loadedFiles[] = $file;
|
|
}
|
|
foreach($config->styles as $file) {
|
|
if(!preg_match($regex, $file)) continue;
|
|
$loadedFiles[] = $file;
|
|
}
|
|
foreach($config->js() as $key => $value) {
|
|
$loadedJSConfig[] = $key;
|
|
}
|
|
$this->sessionSet('loadedFiles', $loadedFiles);
|
|
$this->sessionSet('loadedJSConfig', $loadedJSConfig);
|
|
}
|
|
|
|
/**
|
|
* Return a markup string with scripts that load external assets for an ajax request
|
|
*
|
|
* @return string
|
|
*
|
|
*/
|
|
public function renderExternalAssets() {
|
|
|
|
if(method_exists($this->parentClass, 'renderExternalAssets')) {
|
|
return parent::renderExternalAssets();
|
|
}
|
|
|
|
// all the following can be removed after this version of ListerPro is used with PW 2.6.14+
|
|
// because this method has been moved to the core ProcessPageLister.module file
|
|
|
|
$script = '';
|
|
$regex = '!(Inputfield|Fieldtype|Language|Process|Jquery|Markup)!';
|
|
$scriptClose = '';
|
|
$loadedFiles = $this->sessionGet('loadedFiles');
|
|
if(is_null($loadedFiles)) $loadedFiles = array();
|
|
$loadedFilesAdd = array();
|
|
$loadedJSConfig = $this->sessionGet('loadedJSConfig');
|
|
$config = $this->wire('config');
|
|
$out = '';
|
|
|
|
foreach($config->scripts as $file) {
|
|
if(strpos($file, 'ProcessPageLister')) continue;
|
|
if(!preg_match($regex, $file)) continue;
|
|
if(in_array($file, $loadedFiles)) {
|
|
// script was already loaded and can be skipped
|
|
// if($this->wire('config')->debug) $script .= "\nconsole.log('skip: $file');";
|
|
} else {
|
|
// new script that needs loading
|
|
//$script .= "\n<script src='$file'></script>";
|
|
if($script) $script .= "\n";
|
|
$script .= "$.getScript('$file', function(data, textStatus, jqxhr){";
|
|
// "console.log(textStatus); ";
|
|
$scriptClose .= "})";
|
|
$loadedFilesAdd[] = $file;
|
|
}
|
|
}
|
|
$script .= $scriptClose;
|
|
|
|
foreach($config->styles as $file) {
|
|
if(strpos($file, 'ProcessPageLister')) continue;
|
|
if(!preg_match($regex, $file)) continue;
|
|
if(!in_array($file, $loadedFiles)) {
|
|
$script .= "\n$('<link rel=\"stylesheet\" type=\"text/css\" href=\"$file\">').appendTo('head');"; // console.log('$file');</script>";
|
|
$loadedFilesAdd[] = $file;
|
|
}
|
|
}
|
|
|
|
if(count($loadedFilesAdd)) {
|
|
$loadedFiles = array_merge($loadedFiles, $loadedFilesAdd);
|
|
$this->sessionSet('loadedFiles', $loadedFiles);
|
|
}
|
|
|
|
$jsConfig = array();
|
|
foreach($this->wire('config')->js() as $property => $value) {
|
|
if(!in_array($property, $loadedJSConfig)) {
|
|
$loadedJSConfig[] = $property;
|
|
$jsConfig[$property] = $value;
|
|
}
|
|
}
|
|
|
|
if(count($jsConfig)) {
|
|
$script .= "\n\n" . 'var configAdd=';
|
|
$script .= json_encode($jsConfig) . ';';
|
|
$script .= "\n\n" . '$.extend(config, configAdd);';
|
|
//$script .= "console.log(configAdd);";
|
|
$this->sessionSet('loadedJSConfig', $loadedJSConfig);
|
|
}
|
|
|
|
$out .= "<div id='ProcessListerScript'>$script</div>";
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Save editColumns
|
|
*
|
|
* Looks for a $_POST['_changes'] var containing CSV list of changes in format:
|
|
* 123.456,123.456,123.456 where 123 is page ID and 456 is field ID and commas
|
|
* separate each page.field combination.
|
|
*
|
|
*/
|
|
public function ___executeSave() {
|
|
|
|
if($this->editOption == self::editOptionNone) return '';
|
|
$result = array('debug' => array());
|
|
$changes = $this->wire('input')->post('_changes');
|
|
if(empty($changes)) return $result;
|
|
$changes = array_unique(explode(',', $changes));
|
|
$pages = array();
|
|
$pagesChanges = array();
|
|
|
|
foreach($changes as $change) {
|
|
|
|
if(!strpos($change, '.')) continue;
|
|
list($pageID, $fieldID) = explode('.', $change);
|
|
$pageID = (int) $pageID;
|
|
if(!$pageID) continue;
|
|
$page = isset($pages[$pageID]) ? $pages[$pageID] : $this->wire('pages')->get($pageID);
|
|
if(!$page->id || !$page->editable() || $page->isLocked()) continue;
|
|
if(!isset($pages[$pageID])) $pages[$pageID] = $page;
|
|
$suffix = $this->getInputfieldSuffix($page);
|
|
$field = null;
|
|
|
|
if($fieldID === 'name' || $fieldID === 'parent') {
|
|
$fieldName = $fieldID;
|
|
} else {
|
|
$fieldID = (int) $fieldID;
|
|
if(!$fieldID) continue;
|
|
$field = $this->wire('fields')->get($fieldID);
|
|
if(!$field) continue;
|
|
$fieldName = $field->name;
|
|
}
|
|
|
|
if(!$page->id || !$page->editable($fieldName)) continue;
|
|
if($this->editOption == self::editOptionSome && !in_array($fieldID, $this->editColumns)) continue;
|
|
if($page->isLocked()) continue;
|
|
|
|
$page->of(false);
|
|
$this->editorPage = $page;
|
|
|
|
$inputfield = null;
|
|
if($field) {
|
|
$inputfield = $field->getInputfield($page, $suffix);
|
|
} else if($fieldName == 'name') {
|
|
$inputfield = $this->getNameInputfield($page);
|
|
} else if($fieldName == 'parent') {
|
|
$inputfield = $this->getParentInputfield($page);
|
|
}
|
|
if(!$inputfield) continue;
|
|
|
|
$value = $page->getUnformatted($fieldName);
|
|
if($fieldName == 'parent') $value = $value->id;
|
|
$inputfield->attr('value', $value);
|
|
$inputfield->resetTrackChanges(true);
|
|
$inputfield->processInput($this->wire('input')->post);
|
|
|
|
if(!isset($pagesChanges[$page->id])) $pagesChanges[$page->id] = array();
|
|
|
|
if($inputfield->isChanged()) {
|
|
// $valuePrevious = $value;
|
|
if(is_object($value) && $value instanceof LanguagesValueInterface) {
|
|
$value->setFromInputfield($inputfield);
|
|
} else if($fieldName == 'parent') {
|
|
$newParent = $this->wire('pages')->get((int) $inputfield->attr('value'));
|
|
if($newParent->id && $newParent->id != $value && $newParent->addable($page)) $value = $newParent;
|
|
} else {
|
|
$value = $inputfield->attr('value');
|
|
}
|
|
$result['debug'][] = "$fieldName=$value";
|
|
$page->set($fieldName, $value);
|
|
$page->trackChange($fieldName);
|
|
if($value instanceof WireArray) $value->trackChange('value');
|
|
$pagesChanges[$page->id][$fieldName] = $fieldName;
|
|
/*
|
|
array(
|
|
// TMP: change back to just $fieldName after debugging
|
|
'old' => (string) $valuePrevious,
|
|
'new' => (string) $value,
|
|
'new2' => (string) $page->get($fieldName),
|
|
'process' => (string) $this->wire('process')
|
|
);
|
|
*/
|
|
}
|
|
}
|
|
|
|
// commit any temporary files to be permanent
|
|
foreach($pages as $page) {
|
|
/** @var Page $page */
|
|
$this->editorPage = $page;
|
|
foreach($page->template->fieldgroup as $f) {
|
|
if(!$f->type instanceof FieldtypeFile) continue;
|
|
$pagefiles = $page->get($f->name);
|
|
foreach($pagefiles as $pagefile) {
|
|
/** @var Pagefile $pagefile */
|
|
if(!$pagefile->isTemp()) continue;
|
|
$pagefile->isTemp(false);
|
|
$pagesChanges[$page->id][$f->name] = $f->name;
|
|
$page->trackChange($f->name);
|
|
}
|
|
// if limited to 1 file, remove leading file(s) so that only the last remains
|
|
if($f->maxFiles == 1) while(count($pagefiles) > 1) {
|
|
$item = $pagefiles->first();
|
|
$pagefiles->remove($item);
|
|
$page->trackChange($f->name);
|
|
$pagesChanges[$page->id][$f->name] = $f->name;
|
|
}
|
|
}
|
|
}
|
|
|
|
$numErrors = 0;
|
|
$result['changes'] = $pagesChanges;
|
|
|
|
foreach($pagesChanges as $pageID => $changes) {
|
|
$page = $pages[$pageID];
|
|
$this->editorPage = $page;
|
|
$numChanges = count($changes);
|
|
if(!$numChanges) continue;
|
|
if($numChanges == 1 && $this->wire('fields')->get(reset($changes))) {
|
|
reset($changes);
|
|
$change = key($changes);
|
|
if(!$this->wire('pages')->saveField($page, $change)) $numErrors++;
|
|
} else {
|
|
if(!$this->wire('pages')->save($page, array('uncacheAll' => false))) $numErrors++;
|
|
}
|
|
}
|
|
if($numErrors) {
|
|
$result['success'] = false;
|
|
$result['message'] = $this->wire('pages')->errors('all string');
|
|
} else {
|
|
$result['success'] = true;
|
|
}
|
|
|
|
header("Content-type: application/json");
|
|
return json_encode($result);
|
|
}
|
|
|
|
public function renderExtras() {
|
|
$out = '';
|
|
if($this->actions) $out .= $this->actions->render();
|
|
if($this->wire('user')->isSuperuser() && $this->isValid()) {
|
|
$out .= "<div id='ProcessListerConfigTab' title='" . $this->_x('Config', 'tab') . "' class='WireTab'></div>";
|
|
}
|
|
$out .= parent::renderExtras();
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Find what parent templates are active with the current init selectors
|
|
*
|
|
* @return array
|
|
*
|
|
*/
|
|
public function findParentTemplates() {
|
|
|
|
static $cache = array();
|
|
|
|
// if a specific parent is defined, limit to that
|
|
if($this->parent && $this->parent->id) return array($this->parent->template);
|
|
|
|
// if this method was previously called, return cached value
|
|
if(isset($cache[$this->initSelector])) return $cache[$this->initSelector];
|
|
|
|
$parentTemplates = array();
|
|
$pageTemplates = $this->getSelectorTemplates($this->initSelector, true);
|
|
if(!count($pageTemplates)) {
|
|
$cache[$this->initSelector] = $parentTemplates;
|
|
return $parentTemplates;
|
|
}
|
|
$allTemplates = $this->wire('templates');
|
|
|
|
// first determine parent templates from family 'parentTemplates' settings
|
|
foreach($pageTemplates as $key => $template) {
|
|
if(empty($template->parentTemplates)) continue;
|
|
foreach($template->parentTemplates as $id) {
|
|
$id = (int) $id;
|
|
if(isset($parentTemplates[$id])) continue;
|
|
$template = $allTemplates->get($id);
|
|
if($template) $parentTemplates[$id] = $template;
|
|
}
|
|
unset($pageTemplates[$key]);
|
|
}
|
|
|
|
// next determine parent templates from family 'childTemplates' settings
|
|
if(count($pageTemplates)) foreach($allTemplates as $template) {
|
|
if(empty($template->childTemplates)) continue;
|
|
foreach($pageTemplates as $key => $pageTemplate) {
|
|
if(!in_array($pageTemplate->id, $template->childTemplates)) continue;
|
|
$parentTemplates[$template->id] = $template;
|
|
unset($pageTemplates[$key]);
|
|
}
|
|
}
|
|
|
|
// if we have anything leftover, determine it from existing data in the DB
|
|
foreach($pageTemplates as $template) {
|
|
|
|
$sql =
|
|
'SELECT parents.templates_id FROM pages ' .
|
|
'JOIN pages AS parents ON parents.id=pages.parent_id ' .
|
|
'WHERE pages.templates_id=:templateID ' .
|
|
'AND pages.status<=:pageStatus ' .
|
|
'AND parents.status<=:parentStatus ' .
|
|
'GROUP BY parents.templates_id LIMIT 100';
|
|
|
|
$query = $this->wire('database')->prepare($sql);
|
|
$query->bindValue(':templateID', $template->id, PDO::PARAM_INT);
|
|
$query->bindValue(':pageStatus', Page::statusUnpublished, PDO::PARAM_INT);
|
|
$query->bindValue(':parentStatus', Page::statusUnpublished, PDO::PARAM_INT);
|
|
$query->execute();
|
|
|
|
while($row = $query->fetch(PDO::FETCH_NUM)) {
|
|
list($id) = $row;
|
|
$id = (int) $id;
|
|
if(isset($parentTemplates[$id])) continue;
|
|
$template = $allTemplates->get($id);
|
|
if($template) $parentTemplates[$id] = $template;
|
|
}
|
|
}
|
|
|
|
$cache[$this->initSelector] = $parentTemplates;
|
|
|
|
return $parentTemplates;
|
|
}
|
|
|
|
/**
|
|
* Build the Lister filters form
|
|
*
|
|
* @return InputfieldForm
|
|
*
|
|
*/
|
|
protected function buildFiltersForm() {
|
|
$form = parent::buildFiltersForm();
|
|
/** @var InputfieldSelector $f */
|
|
$f = $form->getChildByName('filters');
|
|
if(in_array('collapseFilters', $this->toggles)) $f->collapsed = Inputfield::collapsedYes;
|
|
if(in_array('noNewFilters', $this->toggles)) $f->allowAddRemove = false;
|
|
$f->showFieldLabels = (int) $this->useColumnLabels;
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* Build the columns asmSelect
|
|
*
|
|
*/
|
|
public function buildColumnsField() {
|
|
|
|
/** @var InputfieldAsmSelect $f */
|
|
$f = parent::buildColumnsField();
|
|
if(!$this->isValid()) return $f;
|
|
|
|
$nullPage = new NullPage();
|
|
$options = $f->getOptions();
|
|
$options2 = array();
|
|
$systemColumns = $this->getSystemColumns();
|
|
$systemLabels = $this->systemLabels;
|
|
$useLabels = (bool) $this->useColumnLabels;
|
|
$parentLabel = $this->_('Parent');
|
|
$limitFields = count($this->limitFields) ? $this->limitFields : null;
|
|
/** @var Languages $languages */
|
|
$languages = $this->wire('languages');
|
|
|
|
// specific to the parent.[subfield] properties
|
|
foreach($systemColumns as $name) {
|
|
$value = "parent.$name";
|
|
if($limitFields && !in_array($value, $limitFields)) continue;
|
|
$label = isset($systemLabels[$name]) ? $systemLabels[$name] : $name;
|
|
$label = $parentLabel . ' > ' . $label;
|
|
$options2[$value] = $label;
|
|
}
|
|
|
|
$parentTemplates = $this->findParentTemplates();
|
|
foreach($parentTemplates as $template) {
|
|
foreach($template->fieldgroup as $field) {
|
|
/** @var Field $field */
|
|
if(count($parentTemplates) == 1) {
|
|
$_field = $template->fieldgroup->getField($field->name, true); // get in context
|
|
if($_field) $field = $_field;
|
|
}
|
|
$value = "parent.$field->name";
|
|
if($limitFields && !in_array($value, $limitFields)) continue;
|
|
$label = $parentLabel . ' > ' . $field->getLabel();
|
|
$options2[$value] = $label;
|
|
}
|
|
}
|
|
|
|
$initTemplate = $this->template;
|
|
|
|
/** @var Fields $fields */
|
|
$fields = $this->wire('fields');
|
|
|
|
// all other fields
|
|
foreach($fields as $field) {
|
|
|
|
if(!isset($options[$field->name])) {
|
|
if(!$this->allowColumnField($field)) continue;
|
|
// if paired with an older Lister that may have missed the field (in certain cases) on the first pass
|
|
$options2[$field->name] = $field->getLabel();
|
|
}
|
|
|
|
if($initTemplate) {
|
|
$_field = $initTemplate->fieldgroup->getField($field->name, true); // context
|
|
if($_field) $field = $_field;
|
|
}
|
|
|
|
$info = $field->type->getSelectorInfo($field);
|
|
//$typeName = $field->type->className();
|
|
|
|
if(count($info['subfields'])) {
|
|
foreach($info['subfields'] as $name => $subinfo) {
|
|
$value = "$field->name.$name";
|
|
$label = $field->getLabel() . ' > ';
|
|
if(isset($systemLabels[$name])) {
|
|
$label .= $systemLabels[$name];
|
|
} else if($languages && $field->type instanceof FieldtypeLanguageInterface && strpos($name, 'data') === 0) {
|
|
if($name == 'data') {
|
|
$language = $languages->getDefault();
|
|
$label .= $language->get('title|name');
|
|
} else if(preg_match('/^data(\d+)$/', $name, $matches)) {
|
|
$language = $languages->get((int) $matches[1]);
|
|
$label .= $language->get('title|name');
|
|
} else {
|
|
$label .= $name;
|
|
}
|
|
} else {
|
|
$_f = $this->wire('fields')->get($name);
|
|
if($_f) {
|
|
$label .= $_f->getLabel();
|
|
} else {
|
|
$label .= $name;
|
|
}
|
|
}
|
|
|
|
$options2[$value] = $label;
|
|
}
|
|
|
|
$blankValue = $field->type->getBlankValue($nullPage, $field);
|
|
if($blankValue instanceof Page || $blankValue instanceof PageArray) {
|
|
foreach($systemColumns as $name) {
|
|
$label = $field->getLabel() . ' > ';
|
|
$label .= isset($systemLabels[$name]) ? $systemLabels[$name] : $name;
|
|
$options2["$field->name.$name"] = $label;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ksort($options2);
|
|
foreach($options2 as $value => $label) {
|
|
if($useLabels) $f->addOption($value, $label, array('data-desc' => $value));
|
|
else $f->addOption($value, $value, array('data-desc' => $label));
|
|
}
|
|
$f->attr('value', $this->columns);
|
|
|
|
if(in_array('collapseColumns', $this->toggles)) $f->collapsed = Inputfield::collapsedYes;
|
|
|
|
return $f;
|
|
}
|
|
|
|
public function renderButtons() {
|
|
$out = parent::renderButtons();
|
|
if($this->editOption != self::editOptionNone) {
|
|
if($out) $out .= " ";
|
|
$btn = $this->wire('modules')->get('InputfieldButton');
|
|
$btn->attr('id', 'save_edits');
|
|
$btn->attr('value', $this->_('Save Edits'));
|
|
$btn->addClass('save_edits head_button_clone');
|
|
$btn->icon = 'save';
|
|
$out .= $btn->render();
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Build the Lister table column from a Page and column name
|
|
*
|
|
* @param Page $p
|
|
* @param array $fields
|
|
* @param string $name
|
|
* @param mixed $value
|
|
* @return string
|
|
*
|
|
*/
|
|
protected function buildListerTableCol(Page $p, array $fields, $name, $value = null) {
|
|
|
|
// whether or not to render the editor output in this request
|
|
if($this->editPreload) {
|
|
$getEditor = true;
|
|
} else {
|
|
static $getEditor = null;
|
|
if(is_null($getEditor)) $getEditor = (int) $this->wire('input')->post('editor');
|
|
}
|
|
|
|
if(strpos($name, '.') !== false) {
|
|
list($basename, $subname) = explode('.', $name);
|
|
if($subname == 'data') $subname = '';
|
|
} else {
|
|
$basename = $name;
|
|
$subname = '';
|
|
}
|
|
|
|
if(is_null($value)) $value = $p->getUnformatted($basename);
|
|
|
|
if($value instanceof Pageimages && $this->imageStyle == 0 && !$subname) {
|
|
// this block can be removed when this version of LP requires PW 2.7+
|
|
$value = $value->getArray();
|
|
if($this->imageFirst && count($value) > 1) $value = array_slice($value, 0, 1);
|
|
}
|
|
|
|
$colPreview = parent::buildListerTableCol($p, $fields, $name, $value);
|
|
if($this->editOption == self::editOptionNone) return $colPreview;
|
|
if($this->editOption == self::editOptionSome && !count($this->editColumns)) return $colPreview;
|
|
$tryEditor = true;
|
|
|
|
if(!strlen($colPreview)) {
|
|
$field = $this->wire('fields')->get($name);
|
|
$colPreview .= "<span class='ui-priority-secondary detail'>";
|
|
if($field && !$p->template->fieldgroup->hasField($field)) {
|
|
$colPreview .= $this->_x('N/A', 'na-col-preview'); // Field is not applicable to page
|
|
$tryEditor = false;
|
|
} else {
|
|
$colPreview .= $this->_x('Blank', 'blank-col-preview'); // Field is blank on page
|
|
}
|
|
$colPreview .= "</span>";
|
|
}
|
|
|
|
if($p->isLocked()) $tryEditor = false;
|
|
if(!$tryEditor) return $colPreview;
|
|
|
|
if($getEditor) {
|
|
$colEditor = $this->buildListerTableColEditor($p, $fields, $name, $value);
|
|
} else {
|
|
$colEditor = "<div class='col_editor col_editor_inactive'></div>";
|
|
}
|
|
if(!strlen($colEditor)) return $colPreview;
|
|
|
|
$out =
|
|
"<div class='col_editable'>" .
|
|
"<div class='col_preview'>$colPreview</div>" .
|
|
"$colEditor" .
|
|
"</div>";
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Build the Lister table column editor
|
|
*
|
|
* @param Page $p
|
|
* @param array $fields
|
|
* @param string $name
|
|
* @param mixed $value
|
|
* @return string Returns blank string when editor not available
|
|
*
|
|
*/
|
|
protected function buildListerTableColEditor(Page $p, array $fields, $name, $value) {
|
|
|
|
/** @var Inputfield $inputfield */
|
|
$inputfield = null;
|
|
/** @var Field $field */
|
|
$field = null;
|
|
$suffix = $this->getInputfieldSuffix($p);
|
|
|
|
if($name == 'name') {
|
|
$inputfield = $this->getNameInputfield($p);
|
|
if(!$inputfield) return '';
|
|
$fieldID = 'name';
|
|
|
|
} else if($name == 'parent') {
|
|
$inputfield = $this->getParentInputfield($p);
|
|
if(!$inputfield) return '';
|
|
$fieldID = 'parent';
|
|
|
|
} else {
|
|
$field = isset($fields[$name]) ? $fields[$name] : $this->wire('fields')->get($name);
|
|
if(!$p->editable($name)) return '';
|
|
$fieldID = $field ? $field->id : '';
|
|
if(!$inputfield && empty($field)) return '';
|
|
if($field && $this->editOption == self::editOptionSome && !in_array($field->id, $this->editColumns)) return '';
|
|
if($field && count($this->editFieldtypes) && !in_array($field->type->className(), $this->editFieldtypes)) return '';
|
|
}
|
|
|
|
$this->editorPage = $p;
|
|
if(is_null($value)) $value = $p->getUnformatted($name);
|
|
if(!$inputfield && $field) $inputfield = $field->getInputfield($p, $suffix);
|
|
if(!$inputfield) return '';
|
|
|
|
$inputfield->attr('value', $value);
|
|
$inClass = $inputfield->className();
|
|
$header = '';
|
|
$outReplacements = array(); // text replacements in rendered markup, when applicable
|
|
|
|
// i.e. InputfieldCheckboxes, InputfieldRadios: not enough room for option columns in Lister table
|
|
/** @var InputfieldCheckboxes|InputfieldRadios $inputfield */
|
|
if($inputfield->optionColumns) {
|
|
$inputfield->optionColumns = 0;
|
|
}
|
|
|
|
if($inClass == 'InputfieldPage') {
|
|
/** @var InputfieldPage $inputfield */
|
|
// special handling for Page fields using a findPagesSelector
|
|
$findPagesSelector = $field->get('findPagesSelector');
|
|
if($findPagesSelector && strpos($findPagesSelector, '=page.') !== false) {
|
|
if(preg_match('!([^,\s=]+)\s*=\s*page\.([^,\s]+)[,\s]*!', $findPagesSelector, $matches)) {
|
|
$f = $this->wire('fields')->get($matches[2]);
|
|
if($f) {
|
|
if(!in_array($f->name, $this->columns) || !in_array($f->id, $this->editColumns)) {
|
|
// remove the dependency requirement if dependency field isn't present and editable in the columns
|
|
$inputfield->findPagesSelector = str_replace($matches[0], '', $findPagesSelector);
|
|
} else {
|
|
$outReplacements['!' . $matches[1] . '=\s*page.' . $f->name . '\b!'] = "$matches[1]$suffix=page.{$f->name}$suffix";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if($inClass == 'InputfieldCKEditor') {
|
|
/** @var InputfieldCKEditor $inputfield */
|
|
$inputfield->configName = 'InputfieldCKEditor_' . $field->name;
|
|
|
|
} else if($inputfield instanceof InputfieldItemList) {
|
|
$inClass .= ' InputfieldItemList';
|
|
$header = "<div class='InputfieldHeader'>$inputfield->label</div>";
|
|
}
|
|
|
|
if($field && $field->type instanceof FieldtypeImage) {
|
|
/** @var InputfieldImage $inputfield */
|
|
$inputfield->useImageEditor = false;
|
|
}
|
|
|
|
// render the Inputfield
|
|
$inputfield->renderReady();
|
|
$label = $this->wire('sanitizer')->entities1($inputfield->label);
|
|
$desc = $field ? $field->getDescription() : '';
|
|
$desc = $desc ? "<p class='description'>" . $this->wire('sanitizer')->entities($desc) . '</p>' : '';
|
|
$notes = $field ? $field->getNotes() : '';
|
|
$notes = $notes ? "<p class='notes'>" . $this->wire('sanitizer')->entities($notes) . '</p>' : '';
|
|
$out = $inputfield->render();
|
|
$inClass = trim("$inClass Inputfield_$name $inputfield->wrapClass");
|
|
|
|
// apply any applicable text replacements
|
|
foreach($outReplacements as $find => $replace) {
|
|
if(strpos($find, '!') === 0) {
|
|
$out = preg_replace($find, $replace, $out); // regex
|
|
} else {
|
|
$out = str_replace($find, $replace, $out); // regular
|
|
}
|
|
}
|
|
|
|
// return rendered Inputfield markup
|
|
return "<ul class='Inputfields'><li " .
|
|
"id='wrap_Inputfield_$inputfield->name' " .
|
|
"class='col_editor Inputfield $inClass' " .
|
|
"data-pid='$p->id' " .
|
|
"data-fid='$fieldID' " .
|
|
"data-label='$label'>" .
|
|
$header .
|
|
"<div class='InputfieldContent'>$desc$out$notes</div>" .
|
|
"</li></ul>";
|
|
}
|
|
|
|
/**
|
|
* Get the ListerPro specific suffix to use for an Inputfield for a given Page
|
|
*
|
|
* @param Page $p
|
|
* @return string
|
|
*
|
|
*/
|
|
protected function getInputfieldSuffix(Page $p) {
|
|
return "_LPID$p->id";
|
|
}
|
|
|
|
/**
|
|
* Get the Inputfield module to edit the 'name' property of a Page
|
|
*
|
|
* @param Page $p
|
|
* @return null|Inputfield
|
|
*
|
|
*/
|
|
protected function getNameInputfield(Page $p) {
|
|
|
|
if($this->editOption == self::editOptionSome && !in_array('name', $this->editColumns)) return null;
|
|
if(!$p->editable('name')) return null;
|
|
$suffix = $this->getInputfieldSuffix($p);
|
|
|
|
$inputfield = $this->wire('modules')->get('InputfieldPageName');
|
|
$inputfield->attr('id+name', "name" . $suffix . '__');
|
|
$inputfield->required = $p->id != 1;
|
|
$inputfield->slashUrls = $p->template->slashUrls;
|
|
$inputfield->checkboxSuffix = $suffix;
|
|
$inputfield->parentPage = $p->parent;
|
|
|
|
return $inputfield;
|
|
}
|
|
|
|
/**
|
|
* Get the Inputfield module to edit the 'parent' property of a Page
|
|
*
|
|
* @param Page $p
|
|
* @param int $maxParents
|
|
* @return null|Inputfield
|
|
*
|
|
*/
|
|
protected function getParentInputfield(Page $p, $maxParents = 100) {
|
|
|
|
if($p->id == 1) return null;
|
|
if($this->editOption == self::editOptionSome && !in_array('parent', $this->editColumns)) return null;
|
|
if(!$p->editable('parent')) return null;
|
|
|
|
$parents = $this->getAllowedParents($p, $maxParents);
|
|
|
|
if($parents->count()) {
|
|
// limited number of parents available, so we can use a regular select
|
|
$inputfield = $this->wire('modules')->get('InputfieldSelect');
|
|
foreach($parents as $parent) $inputfield->addOption($parent->id, $parent->get('title|name'));
|
|
$inputfield->attr('value', $p->parent->id);
|
|
} else {
|
|
// large quantity of parents available, so use a PageListSelect
|
|
$inputfield = $this->wire('modules')->get('InputfieldPageListSelect');
|
|
$inputfield->attr('value', $p->parent);
|
|
}
|
|
|
|
$inputfield->label = $this->_('Parent');
|
|
$inputfield->attr('id+name', 'parent' . $this->getInputfieldSuffix($p));
|
|
$inputfield->required = true;
|
|
|
|
return $inputfield;
|
|
}
|
|
|
|
/**
|
|
* Return the parent pages allowed for given $page
|
|
*
|
|
* If there are too many possibilities, an empty PageArray is returned.
|
|
*
|
|
* @param Page $page
|
|
* @param int $maxParents Maximum number of parents you will accept
|
|
* @return PageArray
|
|
*
|
|
*/
|
|
protected function getAllowedParents(Page $page, $maxParents = 100) {
|
|
|
|
$template = $page->template;
|
|
$parents = new PageArray();
|
|
$parents->add($page->parent);
|
|
|
|
// if page isn't allowed to be moved, then just return current parent
|
|
if($template->noMove) return $parents;
|
|
|
|
// if user doesn't have permission to move pages then only return current parent
|
|
if(!$this->wire('user')->hasPermission('page-move', $page)) return $parents;
|
|
|
|
/** @var Templates $templates */
|
|
$templates = $this->wire('templates');
|
|
$parentTemplates = array();
|
|
|
|
// this section populates the parentTemplates array
|
|
if(count($template->parentTemplates)) {
|
|
|
|
// page has specific parent templates that are allowed
|
|
foreach($template->parentTemplates as $tid) {
|
|
$tid = (int) $tid;
|
|
$t = $templates->get($tid);
|
|
if(!$t) continue;
|
|
// verify that parent template also accepts our $page as a child
|
|
if(count($t->childTemplates) && !in_array($template->id, $t->childTemplates)) continue;
|
|
$parentTemplates[$tid] = $tid;
|
|
}
|
|
|
|
// there are specified parent templates, but none resolved, so just return current parent
|
|
if(!count($parentTemplates)) return $parents;
|
|
|
|
} else {
|
|
|
|
// no parent templates specified, iterate all templates to see if any specify page's template as allowed for children
|
|
foreach($templates as $t) {
|
|
if(!count($t->childTemplates)) continue;
|
|
if(!in_array($template->id, $t->childTemplates)) continue;
|
|
$parentTemplates[$t->id] = $t->id;
|
|
}
|
|
}
|
|
|
|
if(!count($parentTemplates)) {
|
|
// anything may be possible, so return blank PageArray
|
|
return new PageArray();
|
|
}
|
|
|
|
// determine how many possible parents there are
|
|
$selector = "include=all, template=" . implode('|', $parentTemplates);
|
|
$qty = $this->wire('pages')->count($selector);
|
|
if($qty > $maxParents) return new PageArray(); // too many possibilities
|
|
|
|
$parents = $this->wire('pages')->find("$selector, sort=title, sort=name");
|
|
$hasCurrent = false;
|
|
|
|
foreach($parents as $parent) {
|
|
// check if parent is the same one we currently have, then it's always allowed
|
|
if($parent->id == $page->parent->id) {
|
|
$hasCurrent = true;
|
|
continue;
|
|
}
|
|
// double check: if user isn't allowed to add this $page to this parent, remove it
|
|
if(!$parent->addable($page)) $parents->remove($parent);
|
|
}
|
|
|
|
if(!$hasCurrent) $parents->prepend($page->parent);
|
|
|
|
return $parents;
|
|
}
|
|
|
|
public function getPage() {
|
|
return $this->editorPage ? $this->editorPage : new NullPage();
|
|
}
|
|
|
|
/**
|
|
* Execute Page Actions
|
|
*
|
|
*/
|
|
public function ___executeActions() {
|
|
if(!$this->isValid()) return 'Product key required';
|
|
return $this->actions->execute();
|
|
}
|
|
|
|
/**
|
|
* Execute individual Lister config
|
|
*
|
|
*/
|
|
public function ___executeConfig() {
|
|
if(!$this->wire('user')->isSuperuser()) throw new WireException('This feature is only available to superuser.');
|
|
if(!$this->isValid()) return '';
|
|
$this->wire('breadcrumbs')->add(new Breadcrumb('../', $this->page->title));
|
|
$this->wire('processHeadline', $this->_('Configure Lister'));
|
|
$listerConfig = new ListerProConfig($this);
|
|
return $listerConfig->buildForm()->render();
|
|
}
|
|
|
|
public function isValid() {
|
|
if(strpos($this->licenseKey, 'PWLP') === 0) return true;
|
|
$this->error("Please provide a valid product key in the ListerPro module settings");
|
|
return false;
|
|
}
|
|
|
|
public function getListerPageByID($id) {
|
|
$moduleID = $this->wire('modules')->getModuleID($this);
|
|
return $this->wire('pages')->get("process=$moduleID, template=admin, id=" . (int) $id);
|
|
}
|
|
|
|
public function getAllListerPages() {
|
|
$moduleID = $this->wire('modules')->getModuleID($this);
|
|
return $this->wire('pages')->find("process=$moduleID, template=admin, sort=created, include=hidden");
|
|
}
|
|
|
|
/**
|
|
* ListerPro Module Configuration Screen
|
|
*
|
|
* @param array $data
|
|
* @return InputfieldWrapper
|
|
*
|
|
*/
|
|
public static function getModuleConfigInputfields(array $data) {
|
|
|
|
$form = new InputfieldWrapper();
|
|
/** @var InputfieldText $f */
|
|
$f = wire('modules')->get('InputfieldText');
|
|
$f->attr('id+name', 'licenseKey');
|
|
|
|
$licenseKey = isset($data['licenseKey']) ? $data['licenseKey'] : '';
|
|
|
|
if(wire('input')->post->licenseKey && wire('input')->post->licenseKey != wire('session')->ListerLicenseKey) {
|
|
// validate
|
|
$http = new WireHttp();
|
|
$license = wire('sanitizer')->text(wire('input')->post->licenseKey);
|
|
$data = array(
|
|
'action' => 'validate',
|
|
'license' => $license,
|
|
'host' => wire('config')->httpHost,
|
|
'ip' => ip2long(wire('session')->getIP())
|
|
);
|
|
$result = $http->post('http://processwire.com/validate-product/', $data);
|
|
|
|
if($result === 'valid') {
|
|
$licenseKey = $license;
|
|
$f->notes = "Validated!";
|
|
wire()->message("ListerPro product key has been validated!");
|
|
|
|
} else {
|
|
$licenseKey = '';
|
|
$error = "Unable to validate product key: $result";
|
|
$f->error($error);
|
|
wire()->error($error);
|
|
}
|
|
}
|
|
|
|
if(empty($licenseKey)) wire('input')->post->__unset('licenseKey');
|
|
|
|
$f->attr('value', $licenseKey);
|
|
$f->required = true;
|
|
$f->label = "Product Key";
|
|
if($licenseKey) $f->label .= " - VALIDATED!";
|
|
$f->attr('value', wire('config')->demo ? 'disabled for demo mode' : $licenseKey);
|
|
$f->icon = $licenseKey ? 'check-square-o' : 'question-circle';
|
|
$f->description = "Paste in your ListerPro product support key.";
|
|
$f->notes = "If you did not purchase the ListerPro for this site, please [purchase a product key here](http://processwire.com/ListerPro/).";
|
|
if($licenseKey) $f->collapsed = Inputfield::collapsedYes;
|
|
$form->add($f);
|
|
|
|
wire('session')->set('ListerLicenseKey', $licenseKey);
|
|
|
|
/** @var InputfieldAsmSelect $f */
|
|
$f = wire('modules')->get('InputfieldAsmSelect');
|
|
$f->attr('name', 'editFieldtypes');
|
|
$f->label = __('Fieldtypes allowed for column editing in ListerPro');
|
|
$f->collapsed = Inputfield::collapsedYes;
|
|
$f->icon = 'pencil';
|
|
$defaultSelected = array();
|
|
$skipDefaults = array('FieldtypePassword', 'FieldtypeRepeater', 'FieldtypePageTable');
|
|
foreach(wire('fieldtypes') as $fieldtype) {
|
|
/** @var Fieldtype $fieldtype */
|
|
$className = $fieldtype->className();
|
|
if(strpos($className, 'FieldtypeFieldset') !== false) continue;
|
|
$info = wire('modules')->getModuleInfoVerbose($fieldtype);
|
|
if(!empty($info['core']) && !in_array($className, $skipDefaults)) $defaultSelected[] = $className;
|
|
$f->addOption($className, $info['title']);
|
|
}
|
|
if(empty($data['editFieldtypes'])) {
|
|
$f->attr('value', $defaultSelected);
|
|
} else {
|
|
$f->attr('value', $data['editFieldtypes']);
|
|
}
|
|
$form->add($f);
|
|
|
|
if($licenseKey) {
|
|
|
|
/** @var ProcessPageListerPro $listerPro */
|
|
$listerPro = wire('modules')->get('ProcessPageListerPro');
|
|
$pagesWithListers = $listerPro->getAllListerPages();
|
|
if(wire('input')->post('licenseKey')) {
|
|
wire('modules')->addHookAfter('saveModuleConfigData', $listerPro, 'processConfigActions');
|
|
}
|
|
|
|
// input to add new lister
|
|
$f = wire('modules')->get('InputfieldText');
|
|
$f->attr('id+name', '_new_lister_title');
|
|
$f->label = __('Add a Lister');
|
|
$f->icon = 'plus-circle';
|
|
$f->description = __('Enter the title for the lister you want to create. A new page containing your Lister will be created in your admin Pages navigation. You may move the page elsewhere if you prefer.'); // Add new Lister description
|
|
$form->add($f);
|
|
|
|
// input to clone existing lister
|
|
if(count($pagesWithListers)) {
|
|
$f = wire('modules')->get('InputfieldSelect');
|
|
$f->attr('name', '_clone_lister');
|
|
$f->label = __('Clone a Lister');
|
|
$f->description = __('To clone a Lister, select the Lister you want to clone here and enter the title of the new Lister in the "Add a Lister" field above.');
|
|
$f->icon = 'copy';
|
|
$f->attr('onchange', "if($(this).val().length) $('#_new_lister_title').focus()");
|
|
$f->collapsed = Inputfield::collapsedYes;
|
|
foreach($pagesWithListers as $p) {
|
|
$f->addOption($p->id, $p->title);
|
|
}
|
|
$form->add($f);
|
|
}
|
|
|
|
// show all EXISTING listers
|
|
if(count($pagesWithListers)) {
|
|
$f = wire('modules')->get('InputfieldMarkup');
|
|
$f->attr('name', '_lister_list');
|
|
$f->label = __('Your Listers');
|
|
/** @var MarkupAdminDataTable $table */
|
|
$table = wire('modules')->get('MarkupAdminDataTable');
|
|
$table->setEncodeEntities(false);
|
|
$table->headerRow(array(
|
|
__('Lister Title'),
|
|
__('Created'),
|
|
__('Actions'),
|
|
' ',
|
|
));
|
|
foreach($pagesWithListers as $p) {
|
|
$table->row(array(
|
|
"<a href='$p->url'>$p->title</a>",
|
|
"<span style='white-space:nowrap;'>" . date(wire('config')->dateFormat, $p->created) . "</span>",
|
|
"<a href='{$p->url}config/'>" . __('configure') . "</a>",
|
|
"<label style='float: right;'>" .
|
|
"<input type='checkbox' onclick='$(\"#delete_confirm\").show()' name='_delete_lister[]' value='$p->id' /> " .
|
|
"<i class='fa fa-trash-o'></i>" .
|
|
"</label>"
|
|
));
|
|
}
|
|
$f->value = $table->render() .
|
|
"<p id='delete_confirm' style='display: none; margin-top: -1em;'>" .
|
|
"<strong>" .
|
|
__('Are you sure you want to delete the checked Lister(s) above?') .
|
|
"</strong><br />" .
|
|
__('Check this box to confirm:') . ' ' .
|
|
"<label style='display:inline;'><input type='checkbox' name='_delete_confirm' value='1' /> " .
|
|
"<i class='fa fa-trash-o'></i></label>" .
|
|
"</p>";
|
|
$form->add($f);
|
|
}
|
|
|
|
}
|
|
return $form;
|
|
}
|
|
|
|
public function processConfigActions(HookEvent $e) {
|
|
|
|
static $level = 0;
|
|
$level++;
|
|
if($level > 1) return;
|
|
if($e) {}
|
|
|
|
// check for NEW listers
|
|
$title = wire('input')->post('_new_lister_title');
|
|
if($title) {
|
|
$cloneID = wire('input')->post('_clone_lister');
|
|
if($cloneID) {
|
|
$clonePage = $this->getListerPageByID($cloneID);
|
|
if($clonePage->id) {
|
|
$this->cloneLister($clonePage, $title);
|
|
}
|
|
} else {
|
|
$this->addNewLister($title);
|
|
}
|
|
}
|
|
|
|
// check for DELETED Listers
|
|
$deleteIDs = wire('input')->post('_delete_lister');
|
|
if(count($deleteIDs)) {
|
|
if(wire('input')->post('_delete_confirm')) {
|
|
foreach($deleteIDs as $pageID) {
|
|
$deletePage = $this->getListerPageByID($pageID);
|
|
if($deletePage->id) $this->deleteLister($deletePage);
|
|
}
|
|
} else {
|
|
$this->error(__('Delete was not confirmed'));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Placeholder for the viewport iframe
|
|
*
|
|
* @param bool $exit If true, execution will stop after this method call.
|
|
*
|
|
*/
|
|
public function executeViewport($exit = true) {
|
|
echo "<pre>
|
|
__ _ __
|
|
/ / (_)____/ /____ _____
|
|
/ / / / ___/ __/ _ \/ ___/
|
|
/ /___/ (__ ) /_/ __/ /
|
|
/_____/_/____/\__/\___/_/ PRO
|
|
\n";
|
|
if($exit) exit;
|
|
}
|
|
|
|
/**
|
|
* Create a new Lister
|
|
*
|
|
* @param string $title Title of Lister to create
|
|
* @return Page Returns the page where the new Lister resides or NullPage on failure
|
|
*
|
|
*/
|
|
public function addNewLister($title) {
|
|
$page = new Page();
|
|
$page->template = 'admin';
|
|
$admin = $this->wire('pages')->get($this->wire('config')->adminRootPageID);
|
|
$parent = $admin->child('name=page, include=all');
|
|
$page->parent = $parent->id ? $parent : $admin;
|
|
$page->process = $this->wire('modules')->get('ProcessPageListerPro');
|
|
$page->title = $title;
|
|
try {
|
|
$page->save();
|
|
$this->message($this->_('Created Lister') . ' - ' . $page->title);
|
|
} catch(Exception $e) {
|
|
$this->error("Error creating lister - " . $e->getMessage());
|
|
$page = new NullPage();
|
|
}
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* Clone the Lister living on $page to a new lister titled $newListerTitle
|
|
*
|
|
* @param Page $page
|
|
* @param $newListerTitle
|
|
* @return Page Returns the new Lister page
|
|
*
|
|
*/
|
|
public function cloneLister(Page $page, $newListerTitle) {
|
|
$newPage = $this->addNewLister($newListerTitle);
|
|
if(!$newPage->id) return $newPage; // NullPage
|
|
if($newPage->parent_id != $page->parent_id) {
|
|
$newPage->parent = $page->parent;
|
|
$newPage->save();
|
|
}
|
|
$configData = $this->wire('modules')->getModuleConfigData($this);
|
|
$settings = isset($configData['settings'][$page->name]) ? $configData['settings'][$page->name] : array();
|
|
$settings['pagename'] = $newPage->name;
|
|
$configData['settings'][$newPage->name] = $settings;
|
|
$this->wire('modules')->saveModuleConfigData($this, $configData);
|
|
$this->message(sprintf($this->_('Cloned Lister: %1$s => %2$s'), $page->name, $newPage->name));
|
|
return $newPage;
|
|
}
|
|
|
|
/**
|
|
* Delete the Lister that exists on the given Page
|
|
*
|
|
* Also deletes the page.
|
|
*
|
|
* @param Page $page
|
|
* @return bool Returns true on success, false on fail.
|
|
*
|
|
*/
|
|
public function deleteLister(Page $page) {
|
|
$configData = $this->wire('modules')->getModuleConfigData($this);
|
|
if(isset($configData['settings'][$page->name])) {
|
|
unset($configData['settings'][$page->name]);
|
|
$this->wire('modules')->saveModuleConfigData($this, $configData);
|
|
}
|
|
$className = $this->className();
|
|
if(((string) $page->getUnformatted('process')) == $className) {
|
|
$this->wire('pages')->delete($page);
|
|
$this->message(sprintf($this->_('Deleted Lister at %s'), $page->path));
|
|
return true;
|
|
} else {
|
|
$this->error(sprintf($this->_('Page %1$s does not appear to be a %2$s'), $page->path, $className));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Install ListerPro: Convert existing pages using Lister to use ListerPro
|
|
*
|
|
*/
|
|
public function ___install() {
|
|
|
|
$data = $this->wire('modules')->getModuleConfigData('ProcessPageLister');
|
|
if(!empty($data)) $this->wire('modules')->saveModuleConfigData('ProcessPageListerPro', $data);
|
|
|
|
$moduleID = $this->wire('modules')->getModuleID('ProcessPageLister');
|
|
if(!$moduleID) return;
|
|
$pages = $this->wire('pages')->find("template=admin, process=$moduleID, include=all");
|
|
foreach($pages as $page) {
|
|
$page->of(false);
|
|
$page->process = $this;
|
|
$page->save('process');
|
|
$this->message("Updated $page->path to use $this");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Uninstall ListerPro: Convert pages using ListerPro back to use Lister
|
|
*
|
|
*/
|
|
public function ___uninstall() {
|
|
$moduleID = $this->wire('modules')->getModuleID($this);
|
|
if(!$moduleID) return;
|
|
$pages = $this->wire('pages')->find("template=admin, process=$moduleID, include=all");
|
|
foreach($pages as $page) {
|
|
$page->of(false);
|
|
$page->process = 'ProcessPageLister';
|
|
$page->save('process');
|
|
$this->message("Reverted $page->path to use ProcessPageLister (rather than ProcessPageListerPro).");
|
|
}
|
|
}
|
|
|
|
public function ___upgrade($fromVersion, $toVersion) {
|
|
// also trigger any pending upgrades from ProcessPageLister
|
|
$this->wire('modules')->get('ProcessPageLister');
|
|
}
|
|
|
|
|
|
}
|
|
|