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.
758 lines
24 KiB
PHTML
758 lines
24 KiB
PHTML
7 years ago
|
<?php
|
||
|
|
||
|
/**
|
||
|
* Lister Pro: Actions Support (ListerProActions)
|
||
|
* __ _ __
|
||
|
* / / (_)____/ /____ _____
|
||
|
* / / / / ___/ __/ _ \/ ___/
|
||
|
* / /___/ (__ ) /_/ __/ /
|
||
|
* /_____/_/____/\__/\___/_/ PRO
|
||
|
*
|
||
|
* This is a commercial module, please do not distribute.
|
||
|
*
|
||
|
* ListerPro for ProcessWire
|
||
|
* Copyright 2017 by Ryan Cramer
|
||
|
* https://processwire.com/ListerPro/
|
||
|
*
|
||
|
* @method string render()
|
||
|
* @method execute()
|
||
|
* @method executeActions()
|
||
|
* @method InputfieldForm buildActionsForm()
|
||
|
* @method buildActions(InputfieldWrapper $form)
|
||
|
* @method processActions(InputfieldForm $form, PageArray $items)
|
||
|
* @method processActionsIft(InputfieldForm $form, array $items)
|
||
|
* @method beginProcessActions(InputfieldForm $form)
|
||
|
* @method finishProcessActions(InputfieldForm $form)
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
class ListerProActions extends Wire {
|
||
|
|
||
|
/**
|
||
|
* @var ProcessPageLister|ProcessPageListerPro
|
||
|
*
|
||
|
*/
|
||
|
protected $lister = null;
|
||
|
|
||
|
/**
|
||
|
* Max number of items per match when processing actions for large amounts of pages
|
||
|
*
|
||
|
*/
|
||
|
protected $batchLimit = 250;
|
||
|
|
||
|
/**
|
||
|
* True when actions are being processed
|
||
|
*
|
||
|
*/
|
||
|
protected $runningActions = false;
|
||
|
|
||
|
/**
|
||
|
* False when messages shouldn't be echoed, for scalability in processing thousands of pages.
|
||
|
*
|
||
|
*/
|
||
|
protected $echoMessages = true;
|
||
|
|
||
|
/**
|
||
|
* Instance of Ift Runner, if installed and used for queued actions
|
||
|
*
|
||
|
* Only set if queued actions have been requested.
|
||
|
*
|
||
|
* @var IftRunner|null
|
||
|
*
|
||
|
*/
|
||
|
protected $ift = null;
|
||
|
|
||
|
public function __construct(ProcessPageListerPro $lister) {
|
||
|
$this->lister = $lister;
|
||
|
}
|
||
|
|
||
|
public function __get($key) {
|
||
|
if($key == 'template') return $this->lister->template;
|
||
|
if($key == 'lister') return $this->lister;
|
||
|
return parent::__get($key);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render the markup for the actions form (hookable)
|
||
|
*
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
public function ___render() {
|
||
|
if(!count($this->lister->allowActions)) return '';
|
||
|
return $this->buildActionsForm()->render();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Execute actions
|
||
|
*
|
||
|
* Output is direct rather than returned.
|
||
|
*
|
||
|
*/
|
||
|
public function ___execute() {
|
||
|
|
||
|
if($this->wire('config')->demo) {
|
||
|
$form = $this->wire('modules')->get('InputfieldForm');
|
||
|
$this->___beginProcessActions($form);
|
||
|
$this->message('...');
|
||
|
sleep(1);
|
||
|
$this->message('...');
|
||
|
sleep(1);
|
||
|
$this->message('...');
|
||
|
sleep(1);
|
||
|
$this->message("This is demo mode, so actions are disabled.");
|
||
|
$this->___finishProcessActions($form);
|
||
|
exit;
|
||
|
}
|
||
|
|
||
|
// check if config was requested
|
||
|
if($this->wire('input')->post->submit_config) $this->session->redirect("../config/");
|
||
|
|
||
|
// check if filter reset was requested
|
||
|
if($this->wire('input')->post->submit_reset) $this->resetAllFilters();
|
||
|
|
||
|
// abort of no action requested
|
||
|
if(!$this->wire('input')->post->run_action) return '';
|
||
|
|
||
|
// if we are still here, then it's time to execute some actions
|
||
|
$this->executeActions();
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* An action that simply resets all filters and redirects back to new/blank lister
|
||
|
*
|
||
|
*/
|
||
|
protected function resetAllFilters() {
|
||
|
$this->lister->sessionClear();
|
||
|
$this->message($this->_('All filters have been reset.'));
|
||
|
$this->session->redirect('../');
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Build the Lister actions form
|
||
|
*
|
||
|
* Hooks most likely will want to hook the buildActions method instead,
|
||
|
* but this one remains hookable just in case.
|
||
|
*
|
||
|
* @return InputfieldForm
|
||
|
*
|
||
|
*/
|
||
|
protected function ___buildActionsForm() {
|
||
|
|
||
|
/** @var InputfieldForm $form */
|
||
|
$form = $this->modules->get('InputfieldForm');
|
||
|
$form->attr('id', 'ProcessListerActionsForm');
|
||
|
$form->attr('action', './actions/');
|
||
|
$form->attr('method', 'post');
|
||
|
$form->class .= ' WireTab';
|
||
|
$form->attr('title', $this->_('Actions'));
|
||
|
|
||
|
/** @var InputfieldFieldset $fieldset */
|
||
|
$fieldset = $this->modules->get('InputfieldFieldset');
|
||
|
//$fieldset->collapsed = Inputfield::collapsedYes;
|
||
|
$fieldset->label = $form->attr('title');
|
||
|
$fieldset->icon = 'tasks';
|
||
|
$fieldset->description = $this->_('Actions can make changes to any quantity of pages as a group. As a result, it is always a good idea to make sure your site has a good backup before executing actions.');
|
||
|
if(in_array('collapseActions', $this->lister->toggles)) $fieldset->collapsed = Inputfield::collapsedYes;
|
||
|
//$fieldset->description = $this->_('These actions apply to all pages matching your filters (including all paginations).');
|
||
|
|
||
|
/** @var InputfieldRadios $f */
|
||
|
$f = $this->wire('modules')->get('InputfieldRadios');
|
||
|
$f->attr('id+name', 'actions_items');
|
||
|
$f->label = $this->_('Which pages should the actions apply to?');
|
||
|
$f->addOption('all', $this->_('all pages matching your filters'));
|
||
|
$f->addOption('open', $this->_('selected page(s)'));
|
||
|
$f->attr('value', 'all');
|
||
|
$f->collapsed = Inputfield::collapsedYes;
|
||
|
$fieldset->add($f);
|
||
|
|
||
|
$this->buildActions($fieldset);
|
||
|
|
||
|
if($this->modules->isInstalled('IftRunner')) {
|
||
|
/** @var InputfieldRadios $f */
|
||
|
$f = $this->modules->get('InputfieldRadios');
|
||
|
$f->attr('name', 'actions_later');
|
||
|
$f->label = $this->_('When should the action(s) run?');
|
||
|
$f->addOption(0, $this->_('Now'));
|
||
|
$f->addOption(1, $this->_('Later'));
|
||
|
$f->attr('value', 0);
|
||
|
$f->collapsed = Inputfield::collapsedYes;
|
||
|
$f->showIf = 'actions.count>0';
|
||
|
$f->icon = 'clock-o';
|
||
|
$f->columnWidth = 50;
|
||
|
$fieldset->add($f);
|
||
|
|
||
|
$email = $this->wire('user')->email;
|
||
|
$f = $this->wire('modules')->get('InputfieldRadios');
|
||
|
$f->attr('name', 'actions_later_email');
|
||
|
$f->label = $this->_('Notify me when action(s) finish?');
|
||
|
$f->addOption(0, $this->_('Do not notify me'));
|
||
|
if($email) $f->addOption(1, $email);
|
||
|
$f->attr('value', $email ? 1 : 0);
|
||
|
$f->showIf = 'actions_later=1';
|
||
|
$f->columnWidth = 50;
|
||
|
$f->icon = 'envelope-o';
|
||
|
$fieldset->add($f);
|
||
|
}
|
||
|
|
||
|
/** @var InputfieldSubmit $f */
|
||
|
$f = $this->modules->get('InputfieldSubmit');
|
||
|
$f->attr('value', $this->_('Execute'));
|
||
|
$f->attr('name', 'run_action');
|
||
|
$f->icon = 'check';
|
||
|
$fieldset->add($f);
|
||
|
|
||
|
if($this->wire('user')->isSuperuser()) {
|
||
|
$f = $this->modules->get('InputfieldSubmit');
|
||
|
$f->attr('id+name', 'submit_config');
|
||
|
$f->value = $this->_x('Configure Lister', 'button');
|
||
|
$f->class .= ' ui-priority-secondary';
|
||
|
$f->icon = 'cog';
|
||
|
$fieldset->add($f);
|
||
|
}
|
||
|
|
||
|
$f = $this->modules->get('InputfieldSubmit');
|
||
|
$f->attr('id+name', 'submit_reset');
|
||
|
$f->value = $this->_x('Reset Filters', 'button');
|
||
|
$f->class .= ' ui-priority-secondary';
|
||
|
$f->icon = 'minus-circle';
|
||
|
$fieldset->add($f);
|
||
|
|
||
|
$f = $this->modules->get('InputfieldSubmit');
|
||
|
$f->attr('id+name', 'submit_refresh');
|
||
|
$f->value = $this->_x('Refresh Results', 'button');
|
||
|
$f->class .= ' ui-priority-secondary';
|
||
|
$f->icon = 'refresh';
|
||
|
$fieldset->add($f);
|
||
|
|
||
|
/** @var InputfieldMarkup $f */
|
||
|
$f = $this->modules->get('InputfieldMarkup');
|
||
|
$f->attr('id+name', 'wrap_actions_viewport');
|
||
|
$f->label = $this->_('Action Results');
|
||
|
$f->attr('value', "<iframe id='actions_viewport' name='actions_viewport' src='{$this->page->url}/viewport/'></iframe>");
|
||
|
$fieldset->add($f);
|
||
|
|
||
|
$form->add($fieldset);
|
||
|
|
||
|
return $form;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Build the actions to be displayed in the actions form
|
||
|
*
|
||
|
* Hooks shouild hook before or after this method to add any necessary actions
|
||
|
* fields to the form. Hooks should create a collapsed fieldset with their actions
|
||
|
* and then add the fieldset to the given $form.
|
||
|
*
|
||
|
* @param InputfieldWrapper $form The actions fieldset to populate - $event->arguments(0);
|
||
|
*
|
||
|
*/
|
||
|
protected function ___buildActions(InputfieldWrapper $form) {
|
||
|
// for hooks to add whatever actions are needed
|
||
|
|
||
|
$checkboxes = $this->wire('modules')->get('InputfieldCheckboxes');
|
||
|
$checkboxes->attr('name', 'actions');
|
||
|
$checkboxes->label = $this->_('Which actions do you want to run?');
|
||
|
$checkboxes->description = $this->_('For each action you choose, additional configuration options may appear in the fields below.');
|
||
|
$form->add($checkboxes);
|
||
|
$numActions = 0;
|
||
|
|
||
|
foreach($this->lister->allowActions as $className) {
|
||
|
$info = $this->wire('modules')->getModuleInfo($className);
|
||
|
if($info['permission'] && !$this->wire('user')->hasPermission($info['permission'])) continue;
|
||
|
$module = $this->wire('modules')->get($className);
|
||
|
if(!$module instanceof PageAction) continue;
|
||
|
$module->setRunner($this);
|
||
|
$title = $info['title'];
|
||
|
if(stripos($title, 'Page Action:') === 0) $title = trim(str_ireplace('Page Action:', '', $title));
|
||
|
$checkboxes->addOption($className, $title);
|
||
|
$fieldset = $module->getConfigInputfields();
|
||
|
|
||
|
// namespace each action's config settings
|
||
|
foreach($fieldset->getAll() as $inputfield) {
|
||
|
$name = $className . "__" . $inputfield->attr('name');
|
||
|
$inputfield->attr('name', $name);
|
||
|
}
|
||
|
|
||
|
$fieldset->showIf = 'actions=' . $module->className();
|
||
|
$form->add($fieldset);
|
||
|
$numActions++;
|
||
|
}
|
||
|
|
||
|
if(!$numActions) {
|
||
|
$checkboxes->description = $this->_('This Lister does not have any actions assigned or you do not have access to them.');
|
||
|
if($this->wire('user')->isSuperuser()) $checkboxes->description .= ' ' . $this->_('You may add actions from the Lister Config screen.');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process lister actions (all pages)
|
||
|
*
|
||
|
* @param InputfieldForm $form The actions form - $event->arguments(0);
|
||
|
* @param PageArray $items The pages to perform actions on - $event->arguments(1);
|
||
|
*
|
||
|
*/
|
||
|
protected function ___processActions(InputfieldForm $form, PageArray $items) {
|
||
|
|
||
|
$actions = array(); // array of Module objects that we will run
|
||
|
// $inputfields = array();
|
||
|
|
||
|
foreach($form->get('actions')->value as $actionName) {
|
||
|
|
||
|
$actionName = $this->wire('sanitizer')->name($actionName);
|
||
|
if(!in_array($actionName, $this->lister->allowActions)) continue;
|
||
|
$info = $this->wire('modules')->getModuleInfo($actionName);
|
||
|
if($info['permission'] && !$this->wire('user')->hasPermission($info['permission'])) continue;
|
||
|
$action = $this->wire('modules')->get($actionName);
|
||
|
if(!$action || !$action instanceof PageAction) continue;
|
||
|
$action->setRunner($this);
|
||
|
$actions[$actionName] = $action;
|
||
|
$this->message($this->_('Running Action') . " - $actionName");
|
||
|
}
|
||
|
|
||
|
if(!count($actions)) return;
|
||
|
|
||
|
// undo the namespaces to populate the values to each action
|
||
|
foreach($form->getAll() as $inputfield) {
|
||
|
|
||
|
$inputName = $inputfield->attr('name');
|
||
|
if(!strpos($inputName, '__')) continue;
|
||
|
|
||
|
foreach($actions as $actionName => $action) {
|
||
|
if(strpos($inputName, $actionName . '__') !== 0) continue;
|
||
|
$name = str_replace($actionName . '__', '', $inputName);
|
||
|
$value = $inputfield->attr('value');
|
||
|
$action->set($name, $value);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// run actions on all $items now
|
||
|
foreach($actions as $action) {
|
||
|
try {
|
||
|
$echoMessages = $this->echoMessages;
|
||
|
if(!$echoMessages) $this->echoMessages = true;
|
||
|
$action->executeMultiple($items);
|
||
|
if(!$echoMessages) $this->echoMessages = false;
|
||
|
} catch(Exception $e) {
|
||
|
$this->error($e->getMessage());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Queue lister actions for IftRunner (all pages, by ID)
|
||
|
*
|
||
|
* @param InputfieldForm $form The actions form - $event->arguments(0);
|
||
|
* @param array $items The pages to perform actions on - $event->arguments(1);
|
||
|
*
|
||
|
*/
|
||
|
protected function ___processActionsIft(InputfieldForm $form, array $items) {
|
||
|
|
||
|
$actions = array(); // array of Module objects that we will run
|
||
|
// $inputfields = array();
|
||
|
|
||
|
foreach($form->get('actions')->value as $actionName) {
|
||
|
|
||
|
$actionName = $this->wire('sanitizer')->name($actionName);
|
||
|
|
||
|
/** @var WireAction|IftAction $action */
|
||
|
$action = $this->ift->actions->getNew();
|
||
|
$action->title = $this->_('Queued by Lister');
|
||
|
$action->moduleName = $actionName;
|
||
|
if($this->wire('input')->post('actions_later_email')) $action->flags = 512;
|
||
|
$actions[$actionName] = $action;
|
||
|
$echoMessages = $this->echoMessages;
|
||
|
if(!$echoMessages) $this->echoMessages = true;
|
||
|
$this->message($this->_('Action Queued') . " - $actionName - " .
|
||
|
sprintf($this->_('%d pages'), count($items)));
|
||
|
if(!$echoMessages) $this->echoMessages = false;
|
||
|
}
|
||
|
|
||
|
if(!count($actions)) return;
|
||
|
|
||
|
// undo the namespaces to populate the values to each action
|
||
|
foreach($form->getAll() as $inputfield) {
|
||
|
|
||
|
$inputName = $inputfield->attr('name');
|
||
|
if(!strpos($inputName, '__')) continue;
|
||
|
|
||
|
foreach($actions as $actionName => $action) {
|
||
|
if(strpos($inputName, $actionName . '__') !== 0) continue;
|
||
|
$name = str_replace($actionName . '__', '', $inputName);
|
||
|
$value = $inputfield->attr('value');
|
||
|
$action->settings($name, $value);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// queue actions with IftRunner
|
||
|
static $prevActions = array();
|
||
|
foreach($actions as $action) {
|
||
|
$prevAction = isset($prevActions[$action->moduleName]) ? $prevActions[$action->moduleName] : null;
|
||
|
$action = $this->ift->queueAction($action, $items, $prevAction);
|
||
|
$prevActions[$action->moduleName] = $action;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process lister actions (one page at a time)
|
||
|
*
|
||
|
* This method is the one you should hook if you want to process new actions.
|
||
|
* Unlike processLiterActions() this one operates on one page at a time.
|
||
|
* If your hook $event->return is populated with any output, it will be shown.
|
||
|
* Otherwise, your hook should use $this->message() or $this->error() to
|
||
|
* indicate success or errors.
|
||
|
*
|
||
|
* @param InputfieldForm $form The actions form - $event->arguments(0);
|
||
|
* @param Page $page The page to perform actions on - $event->arguments(1);
|
||
|
* @return string This method returns nothing, but hooks may optionally echo results directly (precede each line with a \n).
|
||
|
*
|
||
|
protected function ___processActionsPage(InputfieldForm $form, Page $page) {
|
||
|
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Build batches of page IDs into the database
|
||
|
*
|
||
|
* @param string $itemsCSV CSV string of page IDs to limit to or "all" to run on all (assumed to be unsanitized)
|
||
|
* @return array of database batch IDs
|
||
|
* @throws WireException
|
||
|
*
|
||
|
*/
|
||
|
protected function buildBatches($itemsCSV = 'all') {
|
||
|
|
||
|
$itemIDs = array();
|
||
|
if($itemsCSV != 'all') {
|
||
|
$this->message($this->_('Applying to specific pages'));
|
||
|
$dirtyIDs = explode(',', $itemsCSV);
|
||
|
$valid = true;
|
||
|
foreach($dirtyIDs as $id) {
|
||
|
if(!ctype_digit("$id")) $valid = false;
|
||
|
$itemIDs[] = (int) $id;
|
||
|
}
|
||
|
if(!$valid || !count($itemIDs)) throw new WireException("No pages to run actions on");
|
||
|
} else {
|
||
|
$this->message($this->_('Applying to all pages matching filters'));
|
||
|
}
|
||
|
|
||
|
$database = $this->wire('database');
|
||
|
$selector = $this->lister->getSelector(0);
|
||
|
$start = 0;
|
||
|
$total = 0;
|
||
|
$limit = $this->batchLimit;
|
||
|
$batchIDs = array();
|
||
|
$batchNum = 1;
|
||
|
$pageFinder = new PageFinder();
|
||
|
$allowSorts = array('id', 'name', 'modified', 'created', 'status', 'sort', 'modified_users_id', 'created_users_id');
|
||
|
|
||
|
$selector = $this->lister->removeBlankSelectors($selector);
|
||
|
$selectors = new Selectors($selector);
|
||
|
// $sortChanged = false;
|
||
|
// limit sort to only native fields
|
||
|
foreach($selectors as $s) {
|
||
|
if($s->field == 'sort' && !in_array($s->value, $allowSorts)) {
|
||
|
$selector = preg_replace('/\bsort=[-_a-zA-Z0-9]+/', 'sort=id', $selector);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
while(1) {
|
||
|
if(count($itemIDs)) {
|
||
|
$selectorString = "$selector, id=" . implode('|', $itemIDs); // limit to only selected IDs
|
||
|
} else {
|
||
|
$selectorString = "$selector, start=$start, limit=$limit";
|
||
|
}
|
||
|
$selectors = new Selectors($selectorString);
|
||
|
$options = $total ? array('getTotal' => false) : array();
|
||
|
$items = $pageFinder->find($selectors, $options);
|
||
|
if(!$total) $total = $pageFinder->getTotal();
|
||
|
$start += $limit;
|
||
|
if(!count($items)) break;
|
||
|
$message = $this->_('Building batch of pages') . " [$batchNum/" . ceil($total / $limit) . "]";
|
||
|
if(ProcessPageListerPro::debug) $message .= " $selectorString";
|
||
|
$this->message($message);
|
||
|
$data = array();
|
||
|
foreach($items as $item) $data[] = $item['id'];
|
||
|
$data = implode(',', $data); // to CSV
|
||
|
$sql = "INSERT INTO lister_actions SET data=:data";
|
||
|
$query = $database->prepare($sql);
|
||
|
$query->bindValue(':data', $data);
|
||
|
$query->execute();
|
||
|
$query->closeCursor();
|
||
|
$batchIDs[] = $database->lastInsertId();
|
||
|
$batchNum++;
|
||
|
if(count($itemIDs) && count($itemIDs) < $limit) break;
|
||
|
}
|
||
|
|
||
|
return $batchIDs;
|
||
|
}
|
||
|
|
||
|
protected function getMemoryUsage() {
|
||
|
$unit = array('b','kb','mb','gb','tb','pb');
|
||
|
$size = memory_get_usage(true);
|
||
|
$usage = @round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . $unit[$i];
|
||
|
return $usage;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Execute lister actions
|
||
|
*
|
||
|
* If output is generated it shows the output with a 'return' button.
|
||
|
* If no output is generated then it just returns to the Lister.
|
||
|
*
|
||
|
*/
|
||
|
protected function ___executeActions() {
|
||
|
|
||
|
$form = $this->buildActionsForm();
|
||
|
$form->processInput($this->wire('input')->post);
|
||
|
$this->initTable();
|
||
|
$this->runningActions = true;
|
||
|
$this->beginProcessActions($form);
|
||
|
$this->ift = $this->wire('input')->post('actions_later') ? $this->wire('modules')->get('IftRunner') : null;
|
||
|
|
||
|
$itemsCSV = $this->wire('input')->post('actions_items'); // either "all" or "123,456,789" if page IDs
|
||
|
// $startTime = time();
|
||
|
$numSaved = 0;
|
||
|
$numSkipped = 0;
|
||
|
$numNotChanged = 0;
|
||
|
// $isSuperuser = $this->wire('user')->isSuperuser();
|
||
|
$database = $this->wire('database');
|
||
|
$batchIDs = $this->buildBatches($itemsCSV); // buildBatches knows this is unsanitized
|
||
|
$batchNum = 0;
|
||
|
$batchTotal = count($batchIDs);
|
||
|
$timer = Debug::timer();
|
||
|
$this->message($this->_('Processing batches...'));
|
||
|
if($batchTotal > 10) $this->echoMessages = false;
|
||
|
$templates = $this->lister->getSelectorTemplates($this->lister->getSelector());
|
||
|
$template = count($templates) == 1 ? reset($templates) : null;
|
||
|
|
||
|
foreach($batchIDs as $batchID) {
|
||
|
|
||
|
$batchTimer = Debug::timer();
|
||
|
$batchNum++;
|
||
|
|
||
|
$sql = "SELECT data FROM lister_actions WHERE id=:id";
|
||
|
$query = $database->prepare($sql);
|
||
|
$query->bindValue(':id', $batchID);
|
||
|
$query->execute();
|
||
|
list($data) = $query->fetch(PDO::FETCH_NUM);
|
||
|
$data = explode(',', $data);
|
||
|
$query->closeCursor();
|
||
|
unset($query);
|
||
|
|
||
|
if($this->ift) {
|
||
|
|
||
|
$this->message("processActionsIft", Notice::debug);
|
||
|
$this->processActionsIft($form, $data);
|
||
|
|
||
|
} else {
|
||
|
|
||
|
$items = $this->wire('pages')->getById($data, $template);
|
||
|
foreach($items as $item) $item->of(false);
|
||
|
|
||
|
// run any hooks that process groups of pages
|
||
|
$this->processActions($form, $items);
|
||
|
|
||
|
// save any pages that changed
|
||
|
$n = 0;
|
||
|
$total = count($items);
|
||
|
foreach($items as $item) {
|
||
|
$n++;
|
||
|
|
||
|
$changes = $item->getChanges();
|
||
|
if(!count($changes)) {
|
||
|
if(!$item->statusPrevious && !$item->parentPrevious) $numNotChanged++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(!$item->editable()) {
|
||
|
$this->message(sprintf($this->_('Skipped non-editable page: %s'), $item->path));
|
||
|
$numNotChanged++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$item->of(false);
|
||
|
$changes = implode(', ', $changes);
|
||
|
$this->wire('pages')->save($item, array('uncacheAll' => false));
|
||
|
$numSaved++;
|
||
|
|
||
|
if($this->echoMessages) {
|
||
|
if(!strlen($changes)) $changes = '?';
|
||
|
$this->message(
|
||
|
$this->_('Batch') . " [$batchNum/$batchTotal] " .
|
||
|
$this->_('Page') . " [$n/$total] " .
|
||
|
sprintf($this->_('Saved %s for: %s'), $changes, $item->path)
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
unset($items);
|
||
|
$this->wire('pages')->uncacheAll(); // clear up memory
|
||
|
}
|
||
|
|
||
|
// remove completed batch
|
||
|
$query = $database->prepare("DELETE FROM lister_actions WHERE id=:id");
|
||
|
$query->bindValue(':id', $batchID);
|
||
|
$query->execute();
|
||
|
$query->closeCursor();
|
||
|
unset($query);
|
||
|
|
||
|
$this->echoMessages = true;
|
||
|
$this->message($this->_('Processed batch') . " " .
|
||
|
"[$batchNum/$batchTotal] " . round(Debug::timer($batchTimer), 2) . "s " .
|
||
|
"(" . round(Debug::timer($timer), 2) . "s " . $this->_('total') . ") " .
|
||
|
$this->getMemoryUsage()
|
||
|
);
|
||
|
if($batchTotal > 10) $this->echoMessages = false;
|
||
|
}
|
||
|
|
||
|
$this->echoMessages = true;
|
||
|
|
||
|
if($numSaved) $this->message(sprintf($this->_('%d pages were modified and saved.'), $numSaved));
|
||
|
if($numNotChanged && $this->ift) $this->message(sprintf($this->_('%d pages have been queued.'), $numNotChanged));
|
||
|
else if($numNotChanged) $this->message(sprintf($this->_('%d pages were not modified.'), $numNotChanged));
|
||
|
if($numSkipped) $this->message(sprintf($this->_('%d pages were skipped due to access control.'), $numSkipped));
|
||
|
|
||
|
$this->finishProcessActions($form);
|
||
|
$this->runningActions = false;
|
||
|
exit;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Just update the modified time for the given $page to NOW
|
||
|
*
|
||
|
* @param Page $page
|
||
|
* @param int $time
|
||
|
*
|
||
|
*/
|
||
|
protected function updateModifiedTime(Page $page, $time = NULL) {
|
||
|
if(is_null($time)) $time = time();
|
||
|
$time = date('Y-m-d H:i:s', $time);
|
||
|
$sql = "UPDATE pages SET modified=:time, modified_users_id=:user_id WHERE id=:page_id";
|
||
|
$query = $this->wire('database')->prepare($sql);
|
||
|
$query->bindValue(':page_id', $page->id);
|
||
|
$query->bindValue(':user_id', $this->wire('user')->id);
|
||
|
$query->bindValue(':time', $time);
|
||
|
try {
|
||
|
$query->execute();
|
||
|
} catch(Exception $e) {
|
||
|
$this->error($e->getMessage());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Hook for any actions that want to take place before the built-in functionality, perhaps to override or replaceit.
|
||
|
*
|
||
|
* This is what you'd use if you wanted to output a CSV of all results, for example.
|
||
|
* You would need to perform your own $pages->find($selector). If you intend to override/replace this method
|
||
|
* make sure your hook is a before hook.
|
||
|
*
|
||
|
* @param InputfieldForm $form
|
||
|
*
|
||
|
*/
|
||
|
protected function ___beginProcessActions(InputfieldForm $form) {
|
||
|
if($form) {}
|
||
|
$this->runningActions = true;
|
||
|
set_time_limit(0); // ignore php timeout
|
||
|
while(ob_get_level()) @ob_end_flush(); // remove output buffers
|
||
|
$url = $this->wire('config')->urls->JqueryCore . 'JqueryCore.js';
|
||
|
echo "<!DOCTYPE html><html><head>" .
|
||
|
"<script src='$url'></script>" .
|
||
|
"<script>function bot() { $('body')[0].scrollTop = $('body')[0].scrollHeight; }</script>" .
|
||
|
"<body>";
|
||
|
$this->lister->executeViewport(false);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
public function executeTest() {
|
||
|
$this->runningActions = true;
|
||
|
$form = new InputfieldForm();
|
||
|
$this->beginProcessActions($form, '');
|
||
|
$this->message("Test 1"); sleep(1);
|
||
|
$this->message("Test 2"); sleep(1);
|
||
|
$this->message("Test 3"); sleep(1);
|
||
|
$this->finishProcessActions($form);
|
||
|
exit;
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Hook for any actions that want to take place after all actions are completed.
|
||
|
*
|
||
|
* @param InputfieldForm $form
|
||
|
*
|
||
|
*/
|
||
|
protected function ___finishProcessActions(InputfieldForm $form) {
|
||
|
/*
|
||
|
_______ _ __ __
|
||
|
/ ____(_)___ (_)____/ /_ ___ ____/ /
|
||
|
/ /_ / / __ \/ / ___/ __ \/ _ \/ __ /
|
||
|
/ __/ / / / / / (__ ) / / / __/ /_/ /
|
||
|
/_/ /_/_/ /_/_/____/_/ /_/\___/\__,_/
|
||
|
*/
|
||
|
$finished = $this->_("FINISHED!");
|
||
|
echo "\n$finished
|
||
|
|
||
|
<script>
|
||
|
var spinner=$(parent.document.getElementById('actions_spinner')); spinner.removeClass(spinner.attr('class')).addClass(spinner.attr('data-icon'));
|
||
|
var button=parent.document.getElementById('submit_refresh'); $(button).click();
|
||
|
</script>
|
||
|
";
|
||
|
$this->message(' ');
|
||
|
}
|
||
|
|
||
|
public function message($text, $flags = 0) {
|
||
|
if($this->runningActions) {
|
||
|
if($this->echoMessages) {
|
||
|
echo str_pad("<span>$text</span><!--", 2048, " ") . "--><script>bot();</script><br>";
|
||
|
@ob_flush();
|
||
|
flush();
|
||
|
}
|
||
|
return $this;
|
||
|
} else {
|
||
|
return parent::message($text, $flags);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function error($text, $flags = 0) {
|
||
|
if($this->runningActions) {
|
||
|
echo str_pad("<span style='color: red;'>$text</span><!--", 2048, " ") . "--><script>bot();</script><br>";
|
||
|
@ob_flush();
|
||
|
flush();
|
||
|
return $this;
|
||
|
} else {
|
||
|
return parent::error($text, $flags);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected function initTable() {
|
||
|
|
||
|
$database = $this->wire('database');
|
||
|
$query = $database->prepare("SHOW TABLES LIKE 'lister_actions'");
|
||
|
$query->execute();
|
||
|
|
||
|
if(!$query->rowCount()) {
|
||
|
// create table
|
||
|
$sql = "CREATE TABLE lister_actions (" .
|
||
|
"id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, " .
|
||
|
"created TIMESTAMP NOT NULL, " .
|
||
|
"data TEXT NOT NULL," .
|
||
|
"INDEX created (created)" .
|
||
|
")";
|
||
|
$database->exec($sql);
|
||
|
|
||
|
} else {
|
||
|
// delete old stale data
|
||
|
$created = date('Y-m-d H:i:s', strtotime("-1 WEEK"));
|
||
|
$query = $database->prepare("DELETE FROM lister_actions WHERE created<:created");
|
||
|
$query->bindValue(':created', $created);
|
||
|
$query->execute();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|