mirror of
https://github.com/processwire/processwire.git
synced 2025-08-25 23:56:41 +02:00
Update 3 admin themes for live search code, plus some other minor adjustments
This commit is contained in:
@@ -96,9 +96,19 @@ var ProcessWireAdminTheme = {
|
||||
},
|
||||
_renderItem: function(ul, item) {
|
||||
if(item.label == item.template) item.template = '';
|
||||
return $("<li></li>")
|
||||
.append("<a href='" + item.edit_url + "'>" + item.label + " <small>" + item.template + "</small></a>")
|
||||
.appendTo(ul);
|
||||
var $label = $("<span></span>").text(item.label).css('margin-right', '3px');
|
||||
if(item.unpublished) $label.css('text-decoration', 'line-through');
|
||||
if(item.hidden) $label.addClass('ui-priority-secondary');
|
||||
if(typeof item.icon != "undefined" && item.icon.length) {
|
||||
var $icon = $('<i></i>').addClass('fa fa-fw fa-' + item.icon).css('margin-right', '2px');
|
||||
$label.prepend($icon);
|
||||
}
|
||||
var $a = $("<a></a>")
|
||||
.attr('href', item.edit_url)
|
||||
.attr('title', item.tip)
|
||||
.append($label)
|
||||
.append($("<small class='uk-text-muted'></small>").text(item.template));
|
||||
return $("<li></li>").append($a).appendTo(ul);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -118,7 +128,7 @@ var ProcessWireAdminTheme = {
|
||||
$("#topnav").show();
|
||||
},
|
||||
source: function(request, response) {
|
||||
var url = $input.parents('form').attr('data-action') + 'for?get=template_label,title&include=all&admin_search=' + request.term;
|
||||
var url = $input.parents('form').attr('action') + '?q=' + request.term;
|
||||
$.getJSON(url, function(data) {
|
||||
var len = data.matches.length;
|
||||
if(len < data.total) $status.text(data.matches.length + '/' + data.total);
|
||||
@@ -130,7 +140,12 @@ var ProcessWireAdminTheme = {
|
||||
page_id: item.id,
|
||||
template: item.template_label ? item.template_label : '',
|
||||
edit_url: item.editUrl,
|
||||
type: item.type
|
||||
type: item.type,
|
||||
tip: item.tip,
|
||||
unpublished: (typeof item.unpublished != "undefined" ? item.unpublished : false),
|
||||
hidden: (typeof item.hidden != "undefined" ? item.hidden : false),
|
||||
locked: (typeof item.locked != "undefined" ? item.locked : false),
|
||||
icon: (typeof item.icon != "undefined" ? item.icon : '')
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
@@ -1 +1 @@
|
||||
var ProcessWireAdminTheme={init:function(){var b=$("#head_button > button.pw-dropdown-toggle").hide();this.setupCloneButton();ProcessWireAdmin.init();this.setupSearch();this.setupMobile();var a=$("body");if(a.hasClass("hasWireTabs")&&$("ul.WireTabs").length==0){a.removeClass("hasWireTabs")}$("#content").removeClass("pw-fouc-fix");a.removeClass("pw-init").addClass("pw-ready");if(b.length>0){b.show()}},setupCloneButton:function(){if($("body").is(".modal")){return}var b=$("button.pw-head-button, button.head_button_clone");if(b.length==0){return}var a=$("#head_button");if(a.length==0){a=$("<div id='head_button'></div>").prependTo("#breadcrumbs .pw-container")}b.each(function(){var e=$(this);var d=e.parent("a");var c;if(d.length>0){c=e.parent("a").clone(true);a.prepend(c)}else{if(e.hasClass("pw-head-button")||e.hasClass("head_button_clone")){c=e.clone(true);c.attr("data-from_id",e.attr("id")).attr("id",e.attr("id")+"_copy");c.click(function(){$("#"+$(this).attr("data-from_id")).click();return false});a.prepend(c)}}});a.show()},setupSearch:function(){$.widget("custom.adminsearchautocomplete",$.ui.autocomplete,{_renderMenu:function(e,c){var f=this;var d="";e.attr("id","ProcessPageSearchAutocomplete");$.each(c,function(g,h){if(h.type!=d){$("<li>"+h.type+"</li>").addClass("ui-widget-header").appendTo(e);d=h.type}f._renderItemData(e,h)})},_renderItem:function(c,d){if(d.label==d.template){d.template=""}return $("<li></li>").append("<a href='"+d.edit_url+"'>"+d.label+" <small>"+d.template+"</small></a>").appendTo(c)}});var b=$("#ProcessPageSearchQuery");var a=$("#ProcessPageSearchStatus");b.adminsearchautocomplete({minLength:2,position:{my:"right top",at:"right bottom"},search:function(c,d){a.html("<img src='"+ProcessWire.config.urls.modules+"Process/ProcessPageList/images/loading.gif'>")},open:function(c,d){$("#topnav").hide()},close:function(c,d){$("#topnav").show()},source:function(e,c){var d=b.parents("form").attr("data-action")+"for?get=template_label,title&include=all&admin_search="+e.term;$.getJSON(d,function(g){var f=g.matches.length;if(f<g.total){a.text(g.matches.length+"/"+g.total)}else{a.text(f)}c($.map(g.matches,function(h){return{label:h.title,value:h.title,page_id:h.id,template:h.template_label?h.template_label:"",edit_url:h.editUrl,type:h.type}}))})},select:function(c,d){if(typeof c.key!="undefined"){c.preventDefault();window.location=d.item.edit_url}}}).focus(function(){$(this).siblings("label").find("i").hide()}).blur(function(){a.text("");$(this).siblings("label").find("i").show()})},setupMobile:function(){var a=0;var c=0;var b=function(){var h=$("#topnav");var g=$("body");var e=h.height();if(e>50){if(!g.hasClass("collapse-topnav")){g.addClass("collapse-topnav");a=g.width()}}else{if(a>0){var f=g.width();if(g.hasClass("collapse-topnav")&&f>a){g.removeClass("collapse-topnav");a=0}}}h.children(".collapse-topnav-menu").children("a").click(function(){if($(this).is(".hover")){$(this).mouseleave()}else{$(this).mouseenter()}return false});var d=$(".WireTabs");if(d.length<1){return}d.each(function(){var j=$(this);var i=j.height();if(i>65){if(!g.hasClass("collapse-wiretabs")){g.addClass("collapse-wiretabs");c=g.width()}}else{if(c>0){var k=g.width();if(g.hasClass("collapse-wiretabs")&&k>c){g.removeClass("collapse-wiretabs");c=0}}}})};b();$(window).resize(b)}};$(document).ready(function(){ProcessWireAdminTheme.init();$("a.notice-remove","#notices").click(function(){$("#notices").slideUp("fast",function(){$(this).remove()});return false})});
|
||||
var ProcessWireAdminTheme={init:function(){var b=$("#head_button > button.pw-dropdown-toggle").hide();this.setupCloneButton();ProcessWireAdmin.init();this.setupSearch();this.setupMobile();var a=$("body");if(a.hasClass("hasWireTabs")&&$("ul.WireTabs").length==0){a.removeClass("hasWireTabs")}$("#content").removeClass("pw-fouc-fix");a.removeClass("pw-init").addClass("pw-ready");if(b.length>0){b.show()}},setupCloneButton:function(){if($("body").is(".modal")){return}var b=$("button.pw-head-button, button.head_button_clone");if(b.length==0){return}var a=$("#head_button");if(a.length==0){a=$("<div id='head_button'></div>").prependTo("#breadcrumbs .pw-container")}b.each(function(){var e=$(this);var d=e.parent("a");var c;if(d.length>0){c=e.parent("a").clone(true);a.prepend(c)}else{if(e.hasClass("pw-head-button")||e.hasClass("head_button_clone")){c=e.clone(true);c.attr("data-from_id",e.attr("id")).attr("id",e.attr("id")+"_copy");c.click(function(){$("#"+$(this).attr("data-from_id")).click();return false});a.prepend(c)}}});a.show()},setupSearch:function(){$.widget("custom.adminsearchautocomplete",$.ui.autocomplete,{_renderMenu:function(e,c){var f=this;var d="";e.attr("id","ProcessPageSearchAutocomplete");$.each(c,function(g,h){if(h.type!=d){$("<li>"+h.type+"</li>").addClass("ui-widget-header").appendTo(e);d=h.type}f._renderItemData(e,h)})},_renderItem:function(e,f){if(f.label==f.template){f.template=""}var c=$("<span></span>").text(f.label).css("margin-right","3px");if(f.unpublished){c.css("text-decoration","line-through")}if(f.hidden){c.addClass("ui-priority-secondary")}if(typeof f.icon!="undefined"&&f.icon.length){var d=$("<i></i>").addClass("fa fa-fw fa-"+f.icon).css("margin-right","2px");c.prepend(d)}var g=$("<a></a>").attr("href",f.edit_url).attr("title",f.tip).append(c).append($("<small class='uk-text-muted'></small>").text(f.template));return $("<li></li>").append(g).appendTo(e)}});var b=$("#ProcessPageSearchQuery");var a=$("#ProcessPageSearchStatus");b.adminsearchautocomplete({minLength:2,position:{my:"right top",at:"right bottom"},search:function(c,d){a.html("<img src='"+ProcessWire.config.urls.modules+"Process/ProcessPageList/images/loading.gif'>")},open:function(c,d){$("#topnav").hide()},close:function(c,d){$("#topnav").show()},source:function(e,c){var d=b.parents("form").attr("action")+"?q="+e.term;$.getJSON(d,function(g){var f=g.matches.length;if(f<g.total){a.text(g.matches.length+"/"+g.total)}else{a.text(f)}c($.map(g.matches,function(h){return{label:h.title,value:h.title,page_id:h.id,template:h.template_label?h.template_label:"",edit_url:h.editUrl,type:h.type,tip:h.tip,unpublished:(typeof h.unpublished!="undefined"?h.unpublished:false),hidden:(typeof h.hidden!="undefined"?h.hidden:false),locked:(typeof h.locked!="undefined"?h.locked:false),icon:(typeof h.icon!="undefined"?h.icon:"")}}))})},select:function(c,d){if(typeof c.key!="undefined"){c.preventDefault();window.location=d.item.edit_url}}}).focus(function(){$(this).siblings("label").find("i").hide()}).blur(function(){a.text("");$(this).siblings("label").find("i").show()})},setupMobile:function(){var a=0;var c=0;var b=function(){var h=$("#topnav");var g=$("body");var e=h.height();if(e>50){if(!g.hasClass("collapse-topnav")){g.addClass("collapse-topnav");a=g.width()}}else{if(a>0){var f=g.width();if(g.hasClass("collapse-topnav")&&f>a){g.removeClass("collapse-topnav");a=0}}}h.children(".collapse-topnav-menu").children("a").click(function(){if($(this).is(".hover")){$(this).mouseleave()}else{$(this).mouseenter()}return false});var d=$(".WireTabs");if(d.length<1){return}d.each(function(){var j=$(this);var i=j.height();if(i>65){if(!g.hasClass("collapse-wiretabs")){g.addClass("collapse-wiretabs");c=g.width()}}else{if(c>0){var k=g.width();if(g.hasClass("collapse-wiretabs")&&k>c){g.removeClass("collapse-wiretabs");c=0}}}})};b();$(window).resize(b)}};$(document).ready(function(){ProcessWireAdminTheme.init();$("a.notice-remove","#notices").click(function(){$("#notices").slideUp("fast",function(){$(this).remove()});return false})});
|
@@ -326,9 +326,19 @@ var ProcessWireAdminTheme = {
|
||||
},
|
||||
_renderItem: function(ul, item) {
|
||||
if(item.label == item.template) item.template = '';
|
||||
return $("<li>")
|
||||
.append("<a href='" + item.edit_url + "'>" + item.label + " <small>" + item.template + "</small></a></li>")
|
||||
.appendTo(ul);
|
||||
var $label = $("<span></span>").text(item.label).css('margin-right', '3px');
|
||||
if(item.unpublished) $label.css('text-decoration', 'line-through');
|
||||
if(item.hidden) $label.css('opacity', 0.7);
|
||||
if(item.icon.length) {
|
||||
var $icon = $('<i></i>').addClass('fa fa-fw fa-' + item.icon).css('margin-right', '2px');
|
||||
$label.prepend($icon);
|
||||
}
|
||||
var $a = $("<a></a>")
|
||||
.attr('href', item.edit_url)
|
||||
.attr('title', item.tip)
|
||||
.append($label)
|
||||
.append($("<small class='uk-text-muted'></small>").text(item.template));
|
||||
return $("<li></li>").append($a).appendTo(ul);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -342,7 +352,7 @@ var ProcessWireAdminTheme = {
|
||||
$status.html("<i class='fa fa-spinner fa-spin'></i>");
|
||||
},
|
||||
source: function(request, response) {
|
||||
var url = $input.parents('form').attr('action') + 'for?get=template_label,title&include=all&admin_search=' + request.term;
|
||||
var url = $input.parents('form').attr('action') + '?q=' + request.term;
|
||||
$.getJSON(url, function(data) {
|
||||
var len = data.matches.length;
|
||||
if(len < data.total) $status.text(data.matches.length + '/' + data.total);
|
||||
@@ -354,7 +364,12 @@ var ProcessWireAdminTheme = {
|
||||
page_id: item.id,
|
||||
template: item.template_label ? item.template_label : '',
|
||||
edit_url: item.editUrl,
|
||||
type: item.type
|
||||
type: item.type,
|
||||
tip: item.tip,
|
||||
unpublished: (typeof item.unpublished != "undefined" ? item.unpublished : false),
|
||||
hidden: (typeof item.hidden != "undefined" ? item.hidden : false),
|
||||
locked: (typeof item.locked != "undefined" ? item.locked : false),
|
||||
icon: (typeof item.icon != "undefined" ? item.icon : '')
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@ if(!defined("PROCESSWIRE")) die();
|
||||
/** @var Paths $urls */
|
||||
/** @var AdminThemeUikit $adminTheme */
|
||||
|
||||
$searchURL = $urls->admin . 'page/search/';
|
||||
$searchURL = $urls->admin . 'page/search/live/';
|
||||
|
||||
if($adminTheme->isEditor): ?>
|
||||
<form class='pw-search-form' data-action='<?php echo $searchURL; ?>' action='<?php echo $searchURL; ?>' method='get'>
|
||||
|
@@ -561,6 +561,16 @@ class AdminThemeUikitConfigHelper extends Wire {
|
||||
*/
|
||||
public function configTests(InputfieldWrapper $inputfields) {
|
||||
|
||||
$form = $inputfields->getForm();
|
||||
if($form) {
|
||||
$form->action .= '&tests=1';
|
||||
$this->wire('session')->addHookBefore('redirect', function(HookEvent $event) {
|
||||
$url = $event->arguments(0);
|
||||
$url .= '&tests=1';
|
||||
$event->arguments(0, $url);
|
||||
});
|
||||
}
|
||||
|
||||
/** @var Modules $modules */
|
||||
$modules = $this->wire('modules');
|
||||
|
||||
@@ -705,7 +715,6 @@ class AdminThemeUikitConfigHelper extends Wire {
|
||||
$f->showIf = 'test_select=2';
|
||||
$f->notes = $f->showIf . " ($f->columnWidth%)";
|
||||
$fieldset->add($f);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -301,12 +301,20 @@ var ProcessWireAdminTheme = {
|
||||
},
|
||||
_renderItem: function(ul, item) {
|
||||
if(item.label == item.template) item.template = '';
|
||||
return $("<li></li>")
|
||||
.append(
|
||||
"<a href='" + item.edit_url + "'>" + item.label + " " +
|
||||
"<small class='uk-text-muted'>" + item.template + "</small></a>"
|
||||
)
|
||||
.appendTo(ul);
|
||||
var $label = $("<span></span>").text(item.label).css('margin-right', '3px');
|
||||
if(item.unpublished) $label.css('text-decoration', 'line-through');
|
||||
if(item.hidden) $label.addClass('ui-priority-secondary');
|
||||
if(item.icon.length) {
|
||||
var $icon = $('<i></i>').addClass('fa fa-fw fa-' + item.icon).css('margin-right', '2px');
|
||||
$label.prepend($icon);
|
||||
}
|
||||
var $a = $("<a></a>")
|
||||
.attr('href', item.edit_url)
|
||||
.attr('title', item.tip)
|
||||
.append($label)
|
||||
.append($("<small class='uk-text-muted'></small>").text(item.template));
|
||||
|
||||
return $("<li></li>").append($a).appendTo(ul);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -338,8 +346,7 @@ var ProcessWireAdminTheme = {
|
||||
close: function(event, ui) {
|
||||
},
|
||||
source: function(request, response) {
|
||||
var url = $input.parents('form').attr('data-action') +
|
||||
'for?get=template_label,title&include=all&admin_search=' + request.term;
|
||||
var url = $input.parents('form').attr('data-action') + '?q=' + request.term;
|
||||
$.getJSON(url, function(data) {
|
||||
var len = data.matches.length;
|
||||
if(len < data.total) {
|
||||
@@ -356,7 +363,12 @@ var ProcessWireAdminTheme = {
|
||||
page_id: item.id,
|
||||
template: item.template_label ? item.template_label : '',
|
||||
edit_url: item.editUrl,
|
||||
type: item.type
|
||||
type: item.type,
|
||||
tip: item.tip,
|
||||
unpublished: (typeof item.unpublished != "undefined" ? item.unpublished : false),
|
||||
hidden: (typeof item.hidden != "undefined" ? item.hidden : false),
|
||||
locked: (typeof item.locked != "undefined" ? item.locked : false),
|
||||
icon: (typeof item.icon != "undefined" ? item.icon : '')
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
File diff suppressed because one or more lines are too long
@@ -2651,6 +2651,12 @@ class ProcessField extends Process implements ConfigurableModule {
|
||||
if($property == 'description' || $property == 'all') $search[] = $item->description;
|
||||
if($property == 'notes' || $property == 'all') $search[] = $item->notes;
|
||||
}
|
||||
|
||||
if($property == 'data') {
|
||||
foreach($item->getArray() as $k => $v) {
|
||||
$search[] = (string) $v;
|
||||
}
|
||||
}
|
||||
|
||||
$search = implode(' ', $search);
|
||||
$pos = stripos($search, $text);
|
||||
|
File diff suppressed because it is too large
Load Diff
971
wire/modules/Process/ProcessPageSearch/ProcessPageSearchLive.php
Normal file
971
wire/modules/Process/ProcessPageSearch/ProcessPageSearchLive.php
Normal file
@@ -0,0 +1,971 @@
|
||||
<?php namespace ProcessWire;
|
||||
|
||||
/**
|
||||
* ProcessPageSearch: Live Search (for PW admin)
|
||||
*
|
||||
* @method renderList(array $items, $prefix = 'pw-search', $class = 'list')
|
||||
* @method renderItem(array $item, $prefix = 'pw-search', $class = 'list')
|
||||
*
|
||||
* @todo support searching repeaters
|
||||
*
|
||||
*/
|
||||
|
||||
class ProcessPageSearchLive extends Wire {
|
||||
|
||||
/**
|
||||
* Reference to ProcessPageSearch, if available
|
||||
*
|
||||
* @var Process|ProcessPageSearch
|
||||
*
|
||||
*/
|
||||
protected $process;
|
||||
|
||||
/**
|
||||
* Properties to skip in selectors
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $skipProperties = array(
|
||||
'include',
|
||||
'check_access',
|
||||
'checkaccess',
|
||||
);
|
||||
|
||||
/**
|
||||
* Template for live search settings
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $liveSearchDefaults = array(
|
||||
'type' => '', // type of search, if not pages, i.e. "templates", "fields", "modules", "comments", etc.
|
||||
'property' => '', // property to search for within type, or blank if no specific property
|
||||
'operator' => '%=',
|
||||
'q' => '', // query text to find
|
||||
'selectors' => array(),
|
||||
'template' => null,
|
||||
'multilang' => true,
|
||||
'language' => '', // language name
|
||||
'edit' => true,
|
||||
'start' => 0,
|
||||
'limit' => 15,
|
||||
'verbose' => false,
|
||||
'debug' => false,
|
||||
);
|
||||
|
||||
/**
|
||||
* Template for individual live search result items
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $itemTemplate = array(
|
||||
'id' => 0,
|
||||
'url' => '', // required
|
||||
'name' => '',
|
||||
'title' => '', // required
|
||||
'subtitle' => '',
|
||||
'summary' => '',
|
||||
'icon' => '',
|
||||
'group' => '',
|
||||
'status' => 0,
|
||||
'modified' => 0,
|
||||
);
|
||||
|
||||
/**
|
||||
* Allowed operators
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $allowOperators = array(
|
||||
'=', '==', '!=', '*=', '~=', '%=', '^=', '$=', '<=', '>=', '<', '>'
|
||||
);
|
||||
|
||||
/**
|
||||
* Operator to use for single-word matches (if not overridden)
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
*/
|
||||
protected $singleWordOperator = '%=';
|
||||
|
||||
/**
|
||||
* Operator to use for multi-word matches (if not overridden)
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
*/
|
||||
protected $multiWordOperator = '~=';
|
||||
|
||||
/**
|
||||
* Default fields to search for pages
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $defaultPageSearchFields = array('title');
|
||||
|
||||
/**
|
||||
* Are we currently in “view all” mode?
|
||||
*
|
||||
* @var bool
|
||||
*
|
||||
*/
|
||||
protected $isViewAll = false;
|
||||
|
||||
/**
|
||||
* Order to render results in, by search type
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $searchTypesOrder = array();
|
||||
|
||||
/**
|
||||
* Search types that are specifically excluded
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $noSearchTypes = array();
|
||||
|
||||
/**
|
||||
* PaginatedArray to use for pagination, when applicable for “view all” mode
|
||||
*
|
||||
* @var null|PaginatedArray
|
||||
*
|
||||
*/
|
||||
protected $pagination = null;
|
||||
|
||||
/**
|
||||
* Shared translation labels, defined in constructor
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $labels = array();
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* @param Process|ProcessPageSearch $process
|
||||
* @param array $liveSearch
|
||||
*
|
||||
*/
|
||||
public function __construct(Process $process = null, array $liveSearch = array()) {
|
||||
|
||||
if($process) {
|
||||
$process->wire($this);
|
||||
if($process instanceof ProcessPageSearch) $this->process = $process;
|
||||
$a = explode(' ', $process->searchFields2);
|
||||
if(count($a)) $this->defaultPageSearchFields = $a;
|
||||
}
|
||||
|
||||
if(!empty($liveSearch)) {
|
||||
$this->liveSearchDefaults = array_merge($this->liveSearchDefaults, $liveSearch);
|
||||
}
|
||||
|
||||
$this->labels = array(
|
||||
'missing-query' => $this->_('No search specified'),
|
||||
'pages' => $this->_('Pages'),
|
||||
'trash' => $this->_('Trash'),
|
||||
'modules' => $this->_('Modules'),
|
||||
'view-all' => $this->_('View All'),
|
||||
'search-results' => $this->_('Search Results'),
|
||||
);
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set order of search types
|
||||
*
|
||||
* @param array $types Names of types, in order
|
||||
*
|
||||
*/
|
||||
public function setSearchTypesOrder(array $types) {
|
||||
$this->searchTypesOrder = $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set types that should be excluded unless specifically asked for
|
||||
*
|
||||
* @param array $types Names of types to exclude
|
||||
*
|
||||
*/
|
||||
public function setNoSearchTypes(array $types) {
|
||||
$this->noSearchTypes = $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default operators to use for searches (if query does not specify operator)
|
||||
*
|
||||
* @param string $singleWordOperator
|
||||
* @param string $multiWordOperator
|
||||
*
|
||||
*/
|
||||
public function setDefaultOperators($singleWordOperator, $multiWordOperator = '') {
|
||||
$this->singleWordOperator = $singleWordOperator;
|
||||
$this->multiWordOperator = empty($multiWordOperator) ? $singleWordOperator : $multiWordOperator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize live search
|
||||
*
|
||||
* @param array $presets Additional info to populate in liveSearchInfo
|
||||
* @return array Current liveSearchInfo
|
||||
*
|
||||
*/
|
||||
protected function init(array $presets = array()) {
|
||||
|
||||
/** @var WireInput $input */
|
||||
$input = $this->wire('input');
|
||||
/** @var Sanitizer $sanitizer */
|
||||
$sanitizer = $this->wire('sanitizer');
|
||||
/** @var Fields $fields */
|
||||
$fields = $this->wire('fields');
|
||||
/** @var Templates $templates */
|
||||
$templates = $this->wire('templates');
|
||||
/** @var User $user */
|
||||
$user = $this->wire('user');
|
||||
/** @var Languages $languages */
|
||||
$languages = $this->wire('languages');
|
||||
|
||||
$type = isset($presets['type']) ? $presets['type'] : '';
|
||||
$language = isset($presets['language']) ? $presets['language'] : '';
|
||||
$property = isset($presets['property']) ? $presets['property'] : '';
|
||||
$operator = isset($presets['operator']) ? $presets['operator'] : '';
|
||||
$template = isset($presets['template']) ? $presets['template'] : '';
|
||||
$limit = isset($presets['limit']) ? (int) $presets['limit'] : $this->liveSearchDefaults['limit'];
|
||||
$start = isset($presets['start']) ? (int) $presets['start'] : ($input->pageNum() - 1) * $limit;
|
||||
$selectors = array();
|
||||
$replaceOperator = '';
|
||||
$opHolders = array('<=' => '~@LT=', '>=' => '~@GT=', '<' => '~@LT', '>' => '~@GT'); // operator placeholders
|
||||
|
||||
$q = empty($presets['q']) ? $input->get('q') : $presets['q'];
|
||||
if(empty($q)) $q = $input->get('admin_search'); // legacy name
|
||||
if(strpos($q, '~@') !== false) $q = str_replace('~@', '', $q); // disallow placeholder prefix
|
||||
if(empty($operator)) $q = str_replace(array_keys($opHolders), array_values($opHolders), $q);
|
||||
$q = $sanitizer->text($q, array('reduceSpace' => true));
|
||||
|
||||
if($user->isSuperuser() && strpos($q, 'DEBUG') !== false) {
|
||||
$q = str_replace('DEBUG', '', $q);
|
||||
$presets['debug'] = true;
|
||||
}
|
||||
|
||||
if(empty($q)) {
|
||||
// if no query, we've got nothing to do
|
||||
return $this->liveSearchDefaults;
|
||||
}
|
||||
|
||||
if(empty($operator)) {
|
||||
// operator may be bundled into query: $q
|
||||
if(strpos($q, '~@') !== false) {
|
||||
foreach($opHolders as $op => $placeholder) { // <=, >=, <, >
|
||||
if(strpos($q, $placeholder) === false) continue;
|
||||
$replaceOperator = $placeholder;
|
||||
$operator = $op;
|
||||
break;
|
||||
}
|
||||
|
||||
} else if(strpos($q, '==') !== false) {
|
||||
// forced equals operator
|
||||
$replaceOperator = '==';
|
||||
$operator = '=';
|
||||
|
||||
} else if(strpos($q, '=') !== false) {
|
||||
// regular equals or other w/equals
|
||||
$replaceOperator = '=';
|
||||
if(preg_match('/([%~*^$<>!]{1,2}=)/', $q, $matches)) {
|
||||
if(in_array($matches[1], $this->allowOperators)) {
|
||||
$operator = $matches[1];
|
||||
$replaceOperator = $operator;
|
||||
}
|
||||
} else {
|
||||
// regular equals, use default operator
|
||||
}
|
||||
}
|
||||
|
||||
if($replaceOperator) {
|
||||
$q = str_replace($replaceOperator, ':', $q);
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($operator) || !in_array($operator, $this->allowOperators)) {
|
||||
$operator = strpos($q, ' ') ? $this->multiWordOperator : $this->singleWordOperator;
|
||||
}
|
||||
|
||||
// check if type and property may be part of query: $q
|
||||
if(empty($type) && empty($property) && strpos($q, ':')) {
|
||||
// Search specifies a specific type "type:text", i.e. "users:ryan"
|
||||
list($type, $q) = explode(':', $q, 2);
|
||||
// live search type: pages, users, modules, fields, templates, comments, etc.
|
||||
$type = $sanitizer->name($type);
|
||||
if(strpos($type, '.') !== false) {
|
||||
// live search type includes a property, i.e. "pages.body", "users.first_name", etc.
|
||||
list($type, $property) = explode('.', $type, 2);
|
||||
}
|
||||
if($type === 'pages') {
|
||||
// ok
|
||||
} else if($type) {
|
||||
// check if type refers to a template name or language
|
||||
$template = true;
|
||||
$language = true;
|
||||
} else {
|
||||
// search all types
|
||||
}
|
||||
} else if($type) {
|
||||
$template = true;
|
||||
$language = true;
|
||||
}
|
||||
|
||||
if($language === true) {
|
||||
// check if type refers to a language
|
||||
$language = $languages ? $languages->get($type) : null;
|
||||
if($language && $language->id) {
|
||||
$language = $language->name;
|
||||
$template = null;
|
||||
$type = '';
|
||||
} else {
|
||||
$language = '';
|
||||
}
|
||||
}
|
||||
|
||||
if($template === true) {
|
||||
// check if type refers to template name or language
|
||||
$template = $templates->get($type);
|
||||
}
|
||||
|
||||
if($template && $template instanceof Template) {
|
||||
// does search type match the name of a template?
|
||||
$selectors[] = "template=$template->name";
|
||||
$type = '';
|
||||
// $type = 'pages';
|
||||
}
|
||||
|
||||
$type = $sanitizer->name($type);
|
||||
$property = $sanitizer->fieldName($property);
|
||||
$q = trim($q);
|
||||
$value = $sanitizer->selectorValue($q);
|
||||
$lp = strtolower($property);
|
||||
|
||||
if($property && ($fields->isNative($property) || $fields->get($property)) && !in_array($lp, $this->skipProperties)) {
|
||||
// we recognize this property as searchable, so add it to the selector
|
||||
if($lp == 'status' && !$user->isSuperuser() && $value > Page::statusHidden) $value = Page::statusHidden;
|
||||
$selectors[] = $property . $operator . $value;
|
||||
} else {
|
||||
// we did not recognize the property, so use field(s) defined in module instead
|
||||
$selectors[] = implode('|', $this->defaultPageSearchFields) . $operator . $value;
|
||||
}
|
||||
|
||||
$liveSearch = array_merge($this->liveSearchDefaults, $presets, array(
|
||||
'type' => $type,
|
||||
'property' => $property,
|
||||
'operator' => $operator,
|
||||
'q' => $q,
|
||||
'selectors' => $selectors,
|
||||
'template' => $template,
|
||||
'multilang' => $this->wire('languages') ? true : false,
|
||||
'language' => $language,
|
||||
'start' => $start,
|
||||
'limit' => $limit
|
||||
));
|
||||
|
||||
if($this->isViewAll) {
|
||||
// variables for pagination
|
||||
$input->whitelist('type', $type);
|
||||
$input->whitelist('property', $property);
|
||||
$input->whitelist('operator', $operator);
|
||||
$input->whitelist('q', $q);
|
||||
if(!empty($liveSearch['language'])) $input->whitelist('language', $liveSearch['language']);
|
||||
}
|
||||
|
||||
return $liveSearch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute live search and return JSON result
|
||||
*
|
||||
* @param bool $getJSON Get results as JSON string? Specify false to get array instead.
|
||||
* @return string|array
|
||||
*
|
||||
*/
|
||||
public function execute($getJSON = true) {
|
||||
|
||||
/** @var WireInput $input */
|
||||
$input = $this->wire('input');
|
||||
|
||||
$liveSearch = $this->init();
|
||||
|
||||
if((int) $input->get('version') > 1) {
|
||||
// version 2+ keep results in native format, for future use
|
||||
$items = $this->find($liveSearch);
|
||||
} else {
|
||||
// version 1 is currently used by PW admin themes
|
||||
$items = $this->convertItemsFormat($this->find($liveSearch));
|
||||
}
|
||||
|
||||
$result = array(
|
||||
'matches' => &$items
|
||||
);
|
||||
|
||||
return $getJSON ? json_encode($result) : $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render output for landing page to view all items of a particular type
|
||||
*
|
||||
* Expects these GET vars to be present:
|
||||
* - type
|
||||
* - operator
|
||||
* - property
|
||||
* - q
|
||||
*
|
||||
* @return string
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
public function executeViewAll() {
|
||||
|
||||
/** @var WireInput $input */
|
||||
$input = $this->wire('input');
|
||||
$this->isViewAll = true;
|
||||
|
||||
$type = $input->get->pageName('type');
|
||||
$operator = $input->get('operator');
|
||||
$property = $input->get->fieldName('property');
|
||||
$language = $input->get->pageName('language');
|
||||
$q = $input->get->text('q');
|
||||
$this->pagination = new PaginatedArray();
|
||||
$this->wire($this->pagination);
|
||||
|
||||
if(empty($q)) {
|
||||
$this->error($this->labels['missing-query']);
|
||||
return '';
|
||||
}
|
||||
|
||||
if(false && ($type == 'pages' || $type == 'trash')) {
|
||||
// let Lister handle it
|
||||
$results = array();
|
||||
} else {
|
||||
$liveSearch = $this->init(array(
|
||||
'type' => $type,
|
||||
'property' => $property,
|
||||
'operator' => $operator,
|
||||
'q' => $q,
|
||||
'limit' => $this->liveSearchDefaults['limit'],
|
||||
'verbose' => true,
|
||||
'language' => $language,
|
||||
));
|
||||
$results = $this->find($liveSearch);
|
||||
}
|
||||
|
||||
if($this->process) {
|
||||
$headline = $this->labels['search-results'];
|
||||
if($type) $headline .= " - " . ucfirst($type);
|
||||
$this->process->headline($this->pagination->getPaginationString(array(
|
||||
'label' => $headline,
|
||||
'count' => count($results)
|
||||
)));
|
||||
}
|
||||
|
||||
$out = $this->renderList($results);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform find of types, pages, modules
|
||||
*
|
||||
* Result format that this find method expects from modules it calls the search() method from:
|
||||
*
|
||||
* $result = array(
|
||||
* 'title' => 'Title of these items, used as the group label except where overridden item "group" property',
|
||||
* 'url' => 'URL to view all items', // if omitted, one will be provided automatically
|
||||
* 'total' => 999, // non-paginated total quantity (can be omitted if pagination not supported)
|
||||
* 'items' => [
|
||||
* [
|
||||
* // required properties
|
||||
* 'title' => 'Title of item',
|
||||
* 'url' => 'URL to view or edit the item',
|
||||
* // optional properties:
|
||||
* 'id' => 0,
|
||||
* 'name' => 'Name of item',
|
||||
* 'icon' => 'Optional icon name to represent the item, i.e. "gear" or "fa-gear"',
|
||||
* 'group' => 'Optionally group with other items having this group name, overrides $result[title]',
|
||||
* 'status' => int, // if item is a Page, status of page using Page::status* constants
|
||||
* 'summary' => 'Summary or description of item or excerpt of text that matched', // (recommended)
|
||||
* 'subtitle' => 'Secondary title of item', // (recommended)
|
||||
* 'modified' => int, // last modified date of item
|
||||
* ],
|
||||
* [ ... ], [ ... ], etc.
|
||||
* )
|
||||
* );
|
||||
*
|
||||
* @param array $liveSearch
|
||||
* @return array Array of matches
|
||||
*
|
||||
*/
|
||||
protected function find(array &$liveSearch) {
|
||||
|
||||
$items = array();
|
||||
$user = $this->wire('user');
|
||||
$userLanguage = null;
|
||||
$q = $liveSearch['q'];
|
||||
$type = $liveSearch['type'];
|
||||
$foundTypes = array();
|
||||
$modulesInfo = array();
|
||||
|
||||
/** @var Modules $modules */
|
||||
$modules = $this->wire('modules');
|
||||
|
||||
/** @var Languages $languages */
|
||||
$languages = $this->wire('languages');
|
||||
|
||||
if($languages && $liveSearch['language']) {
|
||||
// change current user to have requested language, temporarily
|
||||
$language = $languages->get($liveSearch['language']);
|
||||
if($language && $language->id) {
|
||||
$userLanguage = $user->language;
|
||||
$user->language = $language;
|
||||
}
|
||||
}
|
||||
|
||||
if($type != 'pages' && $type != 'trash') {
|
||||
$modulesInfo = $modules->getModuleInfo('*', array('verbose' => true));
|
||||
}
|
||||
|
||||
foreach($modulesInfo as $info) {
|
||||
|
||||
if(empty($info['searchable'])) continue;
|
||||
$name = $info['name'];
|
||||
$thisType = $info['searchable'];
|
||||
|
||||
if($type != $thisType && in_array($thisType, $this->noSearchTypes)) continue;
|
||||
if($type && $this->isViewAll && $type != $thisType) continue;
|
||||
if($type && stripos($thisType, $type) === false) continue;
|
||||
if(!empty($liveSearch['template']) && !empty($liveSearch['property'])) continue;
|
||||
if(!$user->isSuperuser() && !$modules->hasPermission($name, $user)) continue;
|
||||
|
||||
$foundTypes[] = $thisType;
|
||||
$module = null;
|
||||
$result = array();
|
||||
$timer = null;
|
||||
|
||||
try {
|
||||
/** @var SearchableModule $module */
|
||||
$module = $modules->getModule($name, array('noInit' => true));
|
||||
if(!$module) continue;
|
||||
$result = $module->search($q, $liveSearch); // see method phpdoc for $result format
|
||||
|
||||
} catch(\Exception $e) {
|
||||
// ok
|
||||
}
|
||||
|
||||
if(!$module || empty($result['items'])) continue;
|
||||
if(empty($result['total'])) $result['total'] = count($result['items']);
|
||||
|
||||
if(!in_array($thisType, $this->searchTypesOrder)) $this->searchTypesOrder[] = $thisType;
|
||||
$order = array_search($thisType, $this->searchTypesOrder);
|
||||
$order = $order * 100;
|
||||
|
||||
$title = empty($result['title']) ? $info['title'] : $result['title'];
|
||||
$n = $liveSearch['start'];
|
||||
$item = null;
|
||||
foreach($result['items'] as $item) {
|
||||
$n++;
|
||||
$item = array_merge($this->itemTemplate, $item);
|
||||
if(empty($item['group'])) $item['group'] = $title;
|
||||
$item['n'] = "$n/$result[total]";
|
||||
$items[$order] = $item;
|
||||
$order++;
|
||||
}
|
||||
if($n && $n < $result['total'] && !$this->isViewAll) {
|
||||
$url = isset($result['url']) ? $result['url'] : '';
|
||||
$items[$order] = $this->makeViewAllItem($liveSearch, $thisType, $item['group'], $result['total'], $url);
|
||||
}
|
||||
if($this->isViewAll && $this->pagination && $type) {
|
||||
$this->pagination->setTotal($result['total']);
|
||||
$this->pagination->setLimit($liveSearch['limit']);
|
||||
$this->pagination->setStart($liveSearch['start']);
|
||||
}
|
||||
}
|
||||
|
||||
if($type && !count($foundTypes) && !in_array($type, array('pages', 'trash', 'modules'))) {
|
||||
if(empty($liveSearch['template']) && !count($foundTypes)) {
|
||||
// if no types matched, and it’s going to skip pages, assume type is a property, and do a pages search
|
||||
$liveSearch = $this->init(array(
|
||||
'q' => $liveSearch['q'],
|
||||
'type' => 'pages',
|
||||
'property' => $type,
|
||||
'operator' => $liveSearch['operator']
|
||||
));
|
||||
$type = 'pages';
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($type) || $type === 'pages' || $type === 'trash' || $liveSearch['template']) {
|
||||
// include pages in the search results
|
||||
if(!in_array('pages', $this->searchTypesOrder)) $this->searchTypesOrder[] = 'pages';
|
||||
$order = array_search('pages', $this->searchTypesOrder) * 100;
|
||||
foreach($this->findPages($liveSearch) as $item) {
|
||||
$items[$order++] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// use built-in modules search when appropriate
|
||||
if((empty($type) || $type == 'modules') && $this->wire('user')->isSuperuser()) {
|
||||
if(!in_array('modules', $this->searchTypesOrder)) $this->searchTypesOrder[] = 'modules';
|
||||
$order = array_search('modules', $this->searchTypesOrder) * 100;
|
||||
foreach($this->findModules($liveSearch, $modulesInfo) as $item) {
|
||||
$items[$order++] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// add a debug item if requested to
|
||||
if(!empty($liveSearch['debug'])) {
|
||||
array_unshift($items, $this->makeDebugItem($liveSearch));
|
||||
}
|
||||
|
||||
if($userLanguage) {
|
||||
// restore original language to user
|
||||
$user->language = $userLanguage;
|
||||
}
|
||||
|
||||
ksort($items);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find pages for live search
|
||||
*
|
||||
* @param array $liveSearch
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
protected function findPages(array &$liveSearch) {
|
||||
|
||||
$user = $this->wire('user');
|
||||
$superuser = $user->isSuperuser();
|
||||
$pages = $this->wire('pages');
|
||||
|
||||
// a $pages->find() search will be included in the live search
|
||||
$selectors = &$liveSearch['selectors'];
|
||||
$selectors[] = "start=$liveSearch[start], limit=$liveSearch[limit]";
|
||||
|
||||
if($this->process) {
|
||||
$repeaterID = $this->process->getRepeatersPageID();
|
||||
if($repeaterID) $selectors[] = "has_parent!=$repeaterID";
|
||||
}
|
||||
|
||||
if($superuser) {
|
||||
// superuser only
|
||||
$selectors[] = "include=all";
|
||||
} else if($user->hasPermission('page-edit')) {
|
||||
// admin search mode and user has some kind of page-edit permission
|
||||
$selectors[] = "include=unpublished";
|
||||
// $selectors[] = "template=$editableTemplates";
|
||||
// $selectors[] = "status<" . Page::statusTrash;
|
||||
} else {
|
||||
// only show regular, non-hidden, non-unpublished pages
|
||||
}
|
||||
|
||||
$selector = implode(', ', $selectors);
|
||||
if($this->process) $selector = $this->process->findReady($selector);
|
||||
|
||||
$titles = array();
|
||||
$items = array();
|
||||
$matches = array('pages' => array(), 'trash' => array());
|
||||
|
||||
try {
|
||||
if(empty($liveSearch['type']) || $liveSearch['type'] == 'pages') {
|
||||
$items['pages'] = $pages->find("$selector, status<" . Page::statusTrash);
|
||||
}
|
||||
} catch(\Exception $e) {
|
||||
}
|
||||
try {
|
||||
if($superuser && (empty($liveSearch['type']) || $liveSearch['type'] == 'trash')) {
|
||||
$items['trash'] = $pages->find("$selector, status>=" . Page::statusTrash);
|
||||
}
|
||||
} catch(\Exception $e) {
|
||||
}
|
||||
|
||||
foreach($items as $type => $pageItems) {
|
||||
|
||||
$n = $liveSearch['start'];
|
||||
$total = $pageItems->getTotal();
|
||||
$item = array();
|
||||
|
||||
foreach($pageItems as $page) {
|
||||
/** @var Page $page */
|
||||
if(!$superuser && $page->isUnpublished() && !$page->editable()) continue;
|
||||
$isAdmin = $page->template == 'admin';
|
||||
$title = (string) $page->get('title|name');
|
||||
|
||||
$item = array(
|
||||
'id' => $page->id,
|
||||
'name' => $page->name,
|
||||
'title' => $title,
|
||||
'subtitle' => $page->template->name,
|
||||
'summary' => $page->path,
|
||||
'url' => !$isAdmin && $page->editable() ? $page->editUrl(array('language' => true)) : $page->url(),
|
||||
'icon' => $page->getIcon(),
|
||||
'group' => '',
|
||||
'n' => (++$n) . '/' . $total,
|
||||
'modified' => $page->modified,
|
||||
'status' => $page->status,
|
||||
);
|
||||
|
||||
if(!isset($titles[$title])) $titles[$title] = 0;
|
||||
$titles[$title]++;
|
||||
$item['group'] = $this->labels[$type];
|
||||
$matches[$type][] = $item;
|
||||
}
|
||||
|
||||
if(!empty($item) && $total > count($matches[$type])) {
|
||||
$matches[$type][] = $this->makeViewAllItem($liveSearch, $type, $item['group'], $total, '');
|
||||
}
|
||||
}
|
||||
|
||||
// merge all the matches together
|
||||
if(empty($matches['trash'])) {
|
||||
$matches = $matches['pages'];
|
||||
} else {
|
||||
$matches = array_merge($matches['pages'], $matches['trash']);
|
||||
}
|
||||
|
||||
// if there any colliding titles, add modified date to the subtitle
|
||||
foreach($titles as $title => $qty) {
|
||||
if($qty < 2) continue;
|
||||
foreach($matches as $key => $item) {
|
||||
if($item['title'] !== $title) continue;
|
||||
$matches[$key]['subtitle'] .= " (" . wireRelativeTimeStr($item['modified'], true) . ")";
|
||||
}
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find modules matching query
|
||||
*
|
||||
* @param array $liveSearch
|
||||
* @param array $modulesInfo
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
protected function findModules(array &$liveSearch, array &$modulesInfo) {
|
||||
|
||||
$q = $liveSearch['q'];
|
||||
$groupLabel = $this->labels['modules'];
|
||||
$items = array();
|
||||
$forceMatch = false;
|
||||
|
||||
if($liveSearch['type'] === 'modules' && !empty($liveSearch['property'])) {
|
||||
// searching for custom module property
|
||||
$forceMatch = true;
|
||||
$infos = $this->wire('modules')->findByInfo(
|
||||
$liveSearch['property'] . $liveSearch['operator'] .
|
||||
$this->wire('sanitizer')->selectorValue($q), 2
|
||||
);
|
||||
} else {
|
||||
// text-matching for all modules
|
||||
$infos = &$modulesInfo;
|
||||
}
|
||||
|
||||
foreach($infos as $info) {
|
||||
$name = $info['name'];
|
||||
$title = $info['title'];
|
||||
if(!$forceMatch) {
|
||||
$searchText = "$name $title $info[summary]";
|
||||
if(stripos($searchText, $q) === false) continue;
|
||||
}
|
||||
$item = array(
|
||||
'id' => $info['id'],
|
||||
'name' => $name,
|
||||
'title' => $title,
|
||||
'subtitle' => $name,
|
||||
'summary' => $info['summary'],
|
||||
'url' => $this->wire('config')->urls->admin . "module/edit?name=$name",
|
||||
'group' => $groupLabel,
|
||||
);
|
||||
$item = array_merge($this->itemTemplate, $item);
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
$total = count($items);
|
||||
$n = 0;
|
||||
foreach($items as $key => $item) {
|
||||
$n++;
|
||||
$items[$key]['n'] = "$n/$total";
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert items from native live search format (v2) to v1 format
|
||||
*
|
||||
* v1 format is used by ProcessWire admin themes.
|
||||
*
|
||||
* @param array $items
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
protected function convertItemsFormat(array $items) {
|
||||
|
||||
$converted = array();
|
||||
$sanitizer = $this->wire('sanitizer');
|
||||
|
||||
foreach($items as $item) {
|
||||
$a = array(
|
||||
'id' => $item['id'],
|
||||
'name' => (string) $item['name'],
|
||||
'title' => (string) $item['title'],
|
||||
'template_label' => (string) $item['subtitle'],
|
||||
'tip' => (string) $item['summary'],
|
||||
'editUrl' => (string) $item['url'],
|
||||
'type' => (string) $sanitizer->entities($item['group']),
|
||||
'icon' => isset($item['icon']) ? $item['icon'] : '',
|
||||
);
|
||||
|
||||
if(!empty($item['status'])) {
|
||||
if($item['status'] & Page::statusUnpublished) $a['unpublished'] = true;
|
||||
if($item['status'] & Page::statusHidden) $a['hidden'] = true;
|
||||
if($item['status'] & Page::statusLocked) $a['locked'] = true;
|
||||
}
|
||||
|
||||
$converted[] = $a;
|
||||
}
|
||||
|
||||
return $converted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a search result item that displays debugging info
|
||||
*
|
||||
* @param array $liveSearch
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
protected function makeDebugItem($liveSearch) {
|
||||
$liveSearch['user_language'] = $this->wire('user')->language->name;
|
||||
$summary = print_r($liveSearch, true);
|
||||
return array_merge($this->itemTemplate, array(
|
||||
'id' => 0,
|
||||
'name' => 'debug',
|
||||
'title' => implode(', ', $liveSearch['selectors']),
|
||||
'subtitle' => $liveSearch['q'],
|
||||
'summary' => $summary,
|
||||
'url' => '#',
|
||||
'group' => 'Debug',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a search result item that displays a “view all” link
|
||||
*
|
||||
* @param array $liveSearch
|
||||
* @param string $type
|
||||
* @param string $group
|
||||
* @param int $total
|
||||
* @param string $url If module provides its own view-all URL
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
protected function makeViewAllItem(&$liveSearch, $type, $group, $total, $url = '') {
|
||||
|
||||
if(!empty($url)) {
|
||||
// use provided url
|
||||
} else if($type == 'pages' || $type == 'trash' || !empty($liveSearch['template'])) {
|
||||
$url = $this->wire('page')->url();
|
||||
$url .= "?q=" . urlencode($liveSearch['q']) . "&live=1";
|
||||
if($type == 'trash') $url .= "&trash=1";
|
||||
if(!empty($liveSearch['template'])) {
|
||||
$url .= "&template=" . $liveSearch['template']->name;
|
||||
}
|
||||
if(!empty($liveSearch['property'])) {
|
||||
$url .= "&field=" . urlencode($liveSearch['property']);
|
||||
}
|
||||
if(!empty($liveSearch['operator'])) {
|
||||
$url .= "&operator=" . urlencode($liveSearch['operator']);
|
||||
}
|
||||
} else {
|
||||
$url = $this->wire('page')->url() . 'live/' .
|
||||
'?q=' . urlencode($liveSearch['q']) .
|
||||
'&type=' . urlencode($type) .
|
||||
'&property=' . urlencode($liveSearch['property']) .
|
||||
'&operator=' . urlencode($liveSearch['operator']);
|
||||
}
|
||||
|
||||
return array_merge($this->itemTemplate, array(
|
||||
'id' => 0,
|
||||
'name' => 'view-all',
|
||||
'title' => $this->labels['view-all'],
|
||||
'subtitle' => sprintf($this->_('%d items'), $total),
|
||||
'summary' => '',
|
||||
'url' => $url,
|
||||
'group' => $group,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render “view all” list
|
||||
*
|
||||
* @param array $items
|
||||
* @param string $prefix For CSS classes, default is "pw-search"
|
||||
* @param string $class Class name for list, default is "list" which translates to "pw-search-list"
|
||||
* @return string HTML markup
|
||||
*
|
||||
*/
|
||||
protected function ___renderList(array $items, $prefix = 'pw-search', $class = 'list') {
|
||||
|
||||
$pagination = $this->pagination->renderPager();
|
||||
$a = array();
|
||||
|
||||
$out = "\n<div class='$class'>" . $pagination;
|
||||
|
||||
foreach($items as $item) {
|
||||
$a[] = $this->renderItem($item, $prefix);
|
||||
}
|
||||
|
||||
$out .= implode('<hr />', $a) . $pagination . "\n</div>";
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an item for the “view all” list
|
||||
*
|
||||
* @param array $item
|
||||
* @param string $prefix For CSS classes, default is "pw-search"
|
||||
* @param string $class Class name for item, default is "item" which translates to "pw-search-item"
|
||||
* @return string HTML markup
|
||||
*
|
||||
*/
|
||||
protected function ___renderItem(array $item, $prefix = 'pw-search', $class = 'item') {
|
||||
|
||||
/** @var Sanitizer $sanitizer */
|
||||
$sanitizer = $this->wire('sanitizer');
|
||||
|
||||
foreach(array('title', 'subtitle', 'summary', 'url') as $key) {
|
||||
if(isset($item[$key])) {
|
||||
$item[$key] = $sanitizer->entities($item[$key]);
|
||||
} else {
|
||||
$item[$key] = '';
|
||||
}
|
||||
}
|
||||
|
||||
$title = "<strong class='$prefix-title'>$item[title]</strong> ";
|
||||
$subtitle = empty($item['subtitle']) ? '' : "<br /><em class='$prefix-subtitle'>$item[subtitle]</em> ";
|
||||
$summary = empty($item['summary']) ? '' : "<br /><span class='$prefix-summary'>$item[summary]</span> ";
|
||||
|
||||
return "\n\t<div class='$prefix-$class'><p><a href='$item[url]'>$title</a> $subtitle $summary</p></div>";
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user