<?php
/**
* @brief Node Controller
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 18 Feb 2013
*/
namespace IPS\Node;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Node Controller
*/
class _Controller extends \IPS\Dispatcher\Controller
{
/**
* Title can contain HTML?
*/
public $_titleHtml = FALSE;
/**
* Description can contain HTML?
*/
public $_descriptionHtml = FALSE;
/**
* @brief If true, will prevent any item from being moved out of its current parent, only allowing them to be reordered within their current parent
*/
protected $lockParents = FALSE;
/**
* @brief If true, root cannot be turned into sub-items, and other items cannot be turned into roots
*/
protected $protectRoots = FALSE;
/**
* Execute
*
* @return void
*/
public function execute()
{
/* Are we sortable? */
$nodeClass = $this->nodeClass;
$this->sortable = $nodeClass::$databaseColumnOrder and $nodeClass::$nodeSortable;
/* Set the title */
$title = $nodeClass::$nodeTitle;
$this->title = $title;
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( $title );
/* Do stuff */
return parent::execute();
}
/**
* Manage
*
* @return void
*/
protected function manage()
{
$nodeClass = $this->nodeClass;
if ( isset( \IPS\Request::i()->searchResult ) )
{
try
{
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global', 'core' )->message( sprintf( \IPS\Member::loggedIn()->language()->get('search_results_in_nodes'), mb_strtolower( \IPS\Member::loggedIn()->language()->get( $nodeClass::$nodeTitle . '_sg' ) ) ), 'information' );
}
catch( \UnderflowException $ex )
{
\IPS\Log::log( $ex->getMessage(), "Language" );
}
}
if ( $nodeClass::$databaseColumnParent === NULL )
{
$this->protectRoots = TRUE;
}
$tree = new \IPS\Helpers\Tree\Tree( $this->url, $nodeClass::$nodeTitle, array( $this, '_getRoots' ), array( $this, '_getRow' ), array( $this, '_getRowParentId' ), array( $this, '_getChildren' ), array( $this, '_getRootButtons' ), TRUE, $this->lockParents, $this->protectRoots );
\IPS\Output::i()->output .= $tree;
}
/**
* Get Root Rows
*
* @return array
*/
public function _getRoots()
{
$nodeClass = $this->nodeClass;
$rows = array();
foreach( $nodeClass::roots( NULL ) as $node )
{
$rows[ $node->_id ] = $this->_getRow( $node );
}
return $rows;
}
/**
* Show the "add" button in the page root rather than the table root
*/
protected $_addButtonInRoot = TRUE;
/**
* Get Root Buttons
*
* @return array
*/
public function _getRootButtons()
{
$nodeClass = $this->nodeClass;
if ( $nodeClass::canAddRoot() )
{
$add = array(
'icon' => 'plus',
'title' => 'add',
'link' => $this->url->setQueryString( 'do', 'form' ),
'data' => ( $nodeClass::$modalForms ? array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('add') ) : array() )
);
if ( \IPS\Request::i()->isAjax() or !$this->_addButtonInRoot )
{
return array( 'add' => $add );
}
else
{
\IPS\Output::i()->sidebar['actions']['add'] = ( array( 'primary' => true ) + $add );
}
}
return array();
}
/**
* Return the custom badge for each row
*
* @param object $node Node returned from $nodeClass::load()
* @return NULL|array Null for no badge, or an array of badge data (0 => CSS class type, 1 => language string, 2 => optional raw HTML to show instead of language string)
*/
public function _getRowBadge( $node )
{
return NULL;
}
/**
* Fetch any additional HTML for this row
*
* @param object $node Node returned from $nodeClass::load()
* @return NULL|string
*/
public function _getRowHtml( $node )
{
return NULL;
}
/**
* Get Single Row
*
* @param mixed $id May be ID number (or key) or an \IPS\Node\Model object
* @param bool $root Format this as the root node?
* @param bool $noSort If TRUE, sort options will be disabled (used for search results)
* @return string
*/
public function _getRow( $id, $root=FALSE, $noSort=FALSE )
{
$nodeClass = $this->nodeClass;
if ( $id instanceof \IPS\Node\Model )
{
$node = $id;
}
else
{
try
{
$node = $nodeClass::load( $id );
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2S101/P', 404, '' );
}
}
$id = ( $node instanceof $nodeClass ) ? $node->_id : "s.{$node->_id}";
$class = get_class( $node );
$buttons = $node->getButtons( $this->url, !( $node instanceof $this->nodeClass ) );
if ( isset( \IPS\Request::i()->searchResult ) and isset( $buttons['edit'] ) )
{
$buttons['edit']['link'] = $buttons['edit']['link']->setQueryString( 'searchResult', \IPS\Request::i()->searchResult );
}
return \IPS\Theme::i()->getTemplate( 'trees', 'core' )->row(
$this->url,
$id,
static::nodeTitle( $node ),
$node->childrenCount( NULL ),
$buttons,
$node->_description,
$node->_icon ? $node->_icon : NULL,
( $noSort === FALSE and $class::$nodeSortable and $node->canEdit() ) ? $node->_position : NULL,
$root,
$node->_enabled,
( $node->_locked or !$node->canEdit() ),
( ( $node instanceof \IPS\Node\Model ) ? $node->_badge : $this->_getRowBadge( $node ) ),
$this->_titleHtml,
$this->_descriptionHtml,
$node->canAdd(),
( $node instanceof $nodeClass ),
$this->_getRowHtml( $node )
);
}
/**
* Get Row parent ID
*
* @param int|string $id Row ID
* @return int|string Parent ID
*/
public function _getRowParentId( $id )
{
$nodeClass = $this->nodeClass;
try
{
return $nodeClass::load( $id )->parent();
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2S101/Q', 404, '' );
}
}
/**
* Get Child Rows
*
* @param int|string $id Row ID
* @return array
*/
public function _getChildren( $id )
{
$rows = array();
$nodeClass = $this->nodeClass;
try
{
$node = $nodeClass::load( $id );
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2S101/R', 404, '' );
}
foreach ( $node->children( NULL ) as $child )
{
$id = ( $child instanceof $this->nodeClass ? '' : 's.' ) . $child->_id;
$rows[ $id ] = $this->_getRow( $child );
}
return $rows;
}
/**
* Add/Edit Form
*
* @return void
*/
protected function form()
{
/* What class are we working with? */
$nodeClass = $this->nodeClass;
$parentNodeClass = NULL;
if ( \IPS\Request::i()->subnode )
{
$parentNodeClass = $nodeClass;
$nodeClass = $nodeClass::$subnodeClass;
}
$node = NULL;
/* Init Edit */
if ( \IPS\Request::i()->id )
{
/* Load the node being edited */
try
{
$node = $nodeClass::load( \IPS\Request::i()->id );
\IPS\Output::i()->title = $node->_title;
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2S101/K', 404, '' );
}
/* Check we have permission to edit it */
if( !$node->canEdit() )
{
\IPS\Output::i()->error( 'node_noperm_edit', '2S101/N', 403, '' );
}
}
/* Init Create */
else
{
/* Create a new object */
$node = new $nodeClass;
/* Set an appropriate title */
if ( !$this->title )
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( $nodeClass::$nodeTitle . '_add_child' );
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( $this->title );
}
/* Are we creating a child of an existing node? */
if ( \IPS\Request::i()->parent )
{
$parentColumn = NULL;
/* Sub node? */
if ( \IPS\Request::i()->subnode )
{
if ( isset( $nodeClass::$parentNodeColumnId ) )
{
try
{
$parent = $parentNodeClass::load( \IPS\Request::i()->parent );
if ( !$parent->canAdd() )
{
\IPS\Output::i()->error( 'node_noperm_edit', '2S101/W', 403, '' );
}
$parentColumn = $nodeClass::$parentNodeColumnId;
}
catch ( \OutOfRangeException $e ) { }
}
}
/* Nope, normal */
elseif ( isset( $nodeClass::$databaseColumnParent ) )
{
try
{
$parent = $nodeClass::load( \IPS\Request::i()->parent );
if ( !$parent->canAdd() )
{
\IPS\Output::i()->error( 'node_noperm_edit', '2S101/V', 403, '' );
}
$parentColumn = $nodeClass::$databaseColumnParent;
}
catch ( \OutOfRangeException $e ) { }
}
/* Set the value */
if ( $parentColumn !== NULL )
{
$node->$parentColumn = \IPS\Request::i()->parent;
}
}
/* No - creating a root - check permission */
else
{
if( !$nodeClass::canAddRoot() )
{
\IPS\Output::i()->error( 'node_noperm_edit', '2S101/U', 403, '' );
}
}
}
/* Build form */
$form = $this->_addEditForm( $node );
/* Handle submissions */
if ( $values = $form->values() )
{
if ( isset( \IPS\Request::i()->massChangeValue ) )
{
\IPS\Output::i()->json( (string) $values[ \IPS\Request::i()->massChangeValue ] );
}
try
{
$new = !$node->_id;
if ( $new and isset( $node::$databaseColumnOrder ) AND $node::$automaticPositionDetermination === TRUE )
{
$orderColumn = $node::$databaseColumnOrder;
$node->$orderColumn = \IPS\Db::i()->select( 'MAX(' . $node::$databasePrefix . $orderColumn . ')', $node::$databaseTable )->first() + 1;
}
$old = NULL;
if ( !$new )
{
$node->skipCloneDuplication = TRUE;
$old = clone $node;
}
$node->saveForm( $node->formatFormValues( $values ) );
if ( $new )
{
\IPS\Session::i()->log( 'acplog__node_created', array( $this->title => TRUE, $node->titleForLog() => FALSE ) );
if ( $node->canManagePermissions() )
{
\IPS\Output::i()->redirect( $this->url->setQueryString( array( 'do' => 'permissions', 'id' => $node->_id, 'subnode' => ( isset( \IPS\Request::i()->subnode ) ? \IPS\Request::i()->subnode : 0 ) ) ) );
}
}
else
{
if ( $node->parent() )
{
foreach( $node->parent()->children() AS $child )
{
$child->setLastComment();
$child->setLastReview();
$child->save();
}
if ( \IPS\Request::i()->subnode )
{
if ( isset( $nodeClass::$parentNodeColumnId ) )
{
$parentColumn = $nodeClass::$parentNodeColumnId;
}
}
elseif ( isset( $nodeClass::$databaseColumnParent ) )
{
$parentColumn = $nodeClass::$databaseColumnParent;
}
$node->$parentColumn = $node->parent()->_id;
$node->setLastComment();
$node->setLastReview();
$node->save();
}
\IPS\Session::i()->log( 'acplog__node_edited', array( $this->title => TRUE, $node->titleForLog() => FALSE ) );
}
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
$this->_afterSave( $old, $node, $form->getLastUsedTab() );
return;
}
catch ( \LogicException $e )
{
$form->error = $e->getMessage();
}
}
/* Display */
\IPS\Output::i()->output .= $form;
}
/**
* Get form
*
* @param \IPS\Node\Model
* @return \IPS\Helpers\Form
*/
protected function _addEditForm( \IPS\Node\Model $node )
{
$form = new \IPS\Helpers\Form( 'form_' . ( $node->_id ?: 'new' ) );
if ( $node->_id AND !$node->noCopyButton )
{
$form->copyButton = $this->url->setQueryString( array( 'do' => 'massChange', 'from' => $node->_id ) );
if ( !( $node instanceof $this->nodeClass ) )
{
$form->copyButton = $form->copyButton->setQueryString( 'subnode', 1 );
}
}
$node->form( $form );
return $form;
}
/**
* Redirect after save
*
* @param \IPS\Node\Model $old A clone of the node as it was before or NULL if this is a creation
* @param \IPS\Node\Model $new The node now
* @param string $lastUsedTab The tab last used in the form
* @return void
*/
protected function _afterSave( \IPS\Node\Model $old = NULL, \IPS\Node\Model $new, $lastUsedTab = FALSE )
{
if( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json( array() );
}
else
{
if( isset( \IPS\Request::i()->save_and_reload ) )
{
$buttons = $new->getButtons( $this->url, !( $new instanceof $this->nodeClass ) );
\IPS\Output::i()->redirect( ( $lastUsedTab ? $buttons['edit']['link']->setQueryString('activeTab', $lastUsedTab ) : $buttons['edit']['link'] ), 'saved' );
}
else
{
\IPS\Output::i()->redirect( $this->url->setQueryString( array( 'root' => ( $new->parent() ? $new->parent()->_id : '' ) ) ), 'saved' );
}
}
}
/**
* Mass Change
*
* @return void
*/
protected function massChange()
{
/* Check permission */
$nodeClass = $this->nodeClass;
if ( \IPS\Request::i()->subnode )
{
$nodeClass = $nodeClass::$subnodeClass;
}
try
{
$node = $nodeClass::load( \IPS\Request::i()->from );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2S101/S', 404, '' );
}
if( !$node->canEdit() )
{
\IPS\Output::i()->error( 'node_noperm_edit', '2S101/T', 403, '' );
}
/* Get the value */
$key = \IPS\Request::i()->key;
$dummyForm = new \IPS\Helpers\Form;
$node->form( $dummyForm );
$value = isset( \IPS\Request::i()->value ) ? \IPS\Request::i()->value : NULL;
if ( $value === NULL )
{
foreach ( $dummyForm->elements as $tab => $elements )
{
if ( isset( $elements[ $key ] ) )
{
$value = $elements[ $key ]->value;
break;
}
}
}
if ( is_array( $value ) )
{
$value = implode( ',', $value );
}
/* Build Form */
$form = new \IPS\Helpers\Form;
$form->ajaxOutput = TRUE;
$field = new \IPS\Helpers\Form\Node( 'nodes', array(), TRUE, array( 'url' => $this->url->setQueryString( array( 'do' => 'massChange', 'key' => $key, 'from' => $node->_id ) ), 'class' => $nodeClass, 'zeroVal' => 'all', 'multiple' => TRUE, 'permissionCheck' => function( $node ) use ( $key, $value )
{
return $node->canCopyValue( $key, $value );
} ) );
$field->label = \IPS\Member::loggedIn()->language()->get( 'copy_value_to' );
$form->add( $field );
/* Display */
if ( $values = $form->values() )
{
$url = $this->url;
$multiRedirectUrl = $this->url->setQueryString( array( 'do' => 'massChange', 'key' => \IPS\Request::i()->key, 'value' => $value, 'nodes' => \IPS\Request::i()->nodes ?: $values['nodes'], 'from' => \IPS\Request::i()->from, 'form_submitted' => 1, 'csrfKey' => \IPS\Request::i()->csrfKey ) );
if ( \IPS\Request::i()->subnode )
{
$multiRedirectUrl = $multiRedirectUrl->setQueryString( 'subnode', 1 );
}
\IPS\Output::i()->output = new \IPS\Helpers\MultipleRedirect( $multiRedirectUrl,
function( $doneSoFar ) use ( $nodeClass )
{
$select = \IPS\Db::i()->select( '*', $nodeClass::$databaseTable, \IPS\Request::i()->nodes == 0 ? NULL : \IPS\Db::i()->in( $nodeClass::$databasePrefix . $nodeClass::$databaseColumnId, explode( ',',\IPS\Request::i()->nodes) ), $nodeClass::$databasePrefix . $nodeClass::$databaseColumnId, array( $doneSoFar, 50 ), NULL, NULL, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
$count = $select->count( TRUE );
if ( !$count )
{
return NULL;
}
$did = 0;
foreach ( $select as $row )
{
$did++;
$_node = $nodeClass::constructFromData( $row );
if ( $_node->canCopyValue( \IPS\Request::i()->key, \IPS\Request::i()->value ) )
{
$values = $_node->formatFormValues( array( \IPS\Request::i()->key => \IPS\Request::i()->value ) );
foreach( $values as $k => $v )
{
$k = preg_replace( '#^' . preg_quote( $nodeClass::$databasePrefix, '#' ) . '#', "", $k );
$val = $_node->$k;
if( is_array( $v ) )
{
foreach( $v as $_k => $_v )
{
$val[ $_k ] = $_v;
}
$_node->$k = $val;
}
else
{
$_node->$k = $v;
}
}
$_node->save();
}
}
if( !$did )
{
return NULL;
}
$doneSoFar += 50;
return array( $doneSoFar, \IPS\Member::loggedIn()->language()->addToStack('copying'), 100 / $count * $doneSoFar );
},
function() use( $url )
{
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
$finishUrl = $url->setQueryString( array( 'do' => 'form', 'id' => \IPS\Request::i()->from ) );
if ( \IPS\Request::i()->subnode )
{
$finishUrl = $finishUrl->setQueryString( 'subnode', 1 );
}
\IPS\Output::i()->redirect( $finishUrl );
},
FALSE
);
}
else
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('global', 'core')->block( '', $form );
}
}
/**
* Toggle Enabled/Disable
*
* @return void
*/
protected function enableToggle()
{
/* Work out which class we're using */
$nodeClass = $this->nodeClass;
if ( mb_substr( \IPS\Request::i()->id, 0, 2 ) === 's.' )
{
\IPS\Request::i()->id = mb_substr( \IPS\Request::i()->id, 2 );
$nodeClass = $nodeClass::$subnodeClass;
}
/* Load Node */
try
{
$node = $nodeClass::load( \IPS\Request::i()->id );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '3S101/A', 404, '' );
}
/* Check we're not locked */
if( $node->_locked or !$node->canEdit() )
{
\IPS\Output::i()->error( 'node_noperm_enable', '2S101/3', 403, '' );
}
/* Toggle */
$node->_enabled = \IPS\Request::i()->status;
$node->save();
/* Recount if needed */
if( $node->parent() )
{
$node->parent()->setLastComment();
$node->parent()->setLastReview();
$node->parent()->save();
}
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
$this->logToggleAndRedirect( $node );
}
/**
* Following a toggle, log the action and redirect. Abstracted so it can be called separately externally.
*
* @param \IPS\Node\Model $node The node we are working with
* @return void
*/
public function logToggleAndRedirect( $node )
{
/* Log */
if ( $node->_enabled )
{
\IPS\Session::i()->log( 'acplog__node_enabled', array( $this->title => TRUE, $node->titleForLog() => FALSE ) );
}
else
{
\IPS\Session::i()->log( 'acplog__node_disabled', array( $this->title => TRUE, $node->titleForLog() => FALSE ) );
}
/* If this is an AJAX request, just respond */
if( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json( $node->_enabled );
}
/* Otherwise, redirect */
else
{
\IPS\Output::i()->redirect( $this->url->setQueryString( array( 'root' => \IPS\Request::i()->root ) ) );
}
}
/**
* Copy
*
* @return void
*/
protected function copy()
{
/* Get node */
$nodeClass = $this->nodeClass;
if ( \IPS\Request::i()->subnode )
{
$nodeClass = $nodeClass::$subnodeClass;
}
try
{
$node = $nodeClass::load( \IPS\Request::i()->id );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2S101/L', 404, '' );
}
/* Do we have any children? */
if ( $node->hasChildren( NULL, NULL, FALSE ) and !isset( \IPS\Request::i()->skipChildren ) )
{
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\YesNo( 'node_copy_children', NULL, FALSE, array(), function( $val ) use ( &$form )
{
if ( $val )
{
$form->ajaxOutput = TRUE;
}
} ) );
if ( $values = $form->values() OR \IPS\Request::i()->node_copy_children )
{
/* Copy Children */
if ( $values['node_copy_children'] OR \IPS\Request::i()->node_copy_children )
{
$ourObj = $this;
$multipleRedirect = new \IPS\Helpers\MultipleRedirect(
$this->url->setQueryString( array( 'do' => 'copy', 'id' => $node->_id, 'subnode' => \IPS\Request::i()->subnode, 'form_submitted' => 1, 'csrfKey' => \IPS\Request::i()->csrfKey, 'node_copy_children' => 1 ) ),
/* Process */
function( $data ) use ( $node, $nodeClass )
{
/* Init */
if ( !is_array( $data ) )
{
return array( array( 'copy' => array( array( 'id' => $node->_id, 'subnode' => 0 ) ), 'ids' => array() ), \IPS\Member::loggedIn()->language()->addToStack('copying') );
}
/* Process */
else
{
/* Have we finished? */
if ( empty( $data['copy'] ) )
{
return NULL;
}
/* No, still going */
foreach( $data['copy'] as $k => $itemData )
{
/* Load */
if ( $itemData['subnode'] )
{
$nodeClass = $nodeClass::$subnodeClass;
}
$item = $nodeClass::load( $itemData['id'] );
/* Copy it */
$new = clone $item;
$data['ids'][ get_class( $item ) ][ $item->_id ] = $new->_id;
/* Update it's parent */
if ( $item->parent() )
{
if ( array_key_exists( $item->parent()->_id, $data['ids'][ get_class( $item->parent() ) ] ) )
{
$parentColumn = $nodeClass::$databaseColumnParent;
if ( $itemData['subnode'] )
{
$parentColumn = $nodeClass::$parentNodeColumnId;
}
$new->$parentColumn = $data['ids'][ get_class( $item->parent() ) ][ $item->parent()->_id ];
$new->save();
}
}
/* Remove this one from our array */
unset( $data['copy'][ $k ] );
/* And add all it's children */
foreach ( $item->children( NULL ) as $child )
{
$data['copy'][] = array( 'id' => $child->_id, 'subnode' => !( $child instanceof $nodeClass ) );
}
/* Return */
return array( $data, \IPS\Member::loggedIn()->language()->addToStack('copying') );
}
}
},
/* Finish */
function() use ( $node, $ourObj )
{
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
\IPS\Session::i()->log( 'acplog__node_copied_c', array( $node->title => TRUE, $node->titleForLog() => FALSE ) );
\IPS\Output::i()->redirect( $ourObj->url->setQueryString( array( 'root' => ( $node->parent() ? $node->parent()->id : '' ) ) ), 'saved' );
}
);
\IPS\Output::i()->output = $multipleRedirect;
return;
}
}
else
{
/* Show form */
\IPS\Output::i()->output = $form;
return;
}
}
/* Copy it */
$new = clone $node;
\IPS\Session::i()->log( 'acplog__node_copied', array( $this->title => TRUE, $node->titleForLog() => FALSE ) );
/* Clear create menu */
\IPS\Member::clearCreateMenu();
/* Boink */
$url = $this->url->setQueryString( array( 'do' => 'form', 'id' => $new->_id ) );
if ( isset( \IPS\Request::i()->subnode ) )
{
$url = $url->setQueryString( 'subnode', 1 );
}
\IPS\Output::i()->redirect( $url, 'copied' );
}
/**
* Reorder
*
* @return void
*/
protected function reorder()
{
/* Init */
$nodeClass = $this->nodeClass;
/* Normalise AJAX vs non-AJAX */
if( isset( \IPS\Request::i()->ajax_order ) )
{
$order = array();
$position = array();
foreach( \IPS\Request::i()->ajax_order as $id => $parent )
{
if ( !isset( $order[ $parent ] ) )
{
$order[ $parent ] = array();
$position[ $parent ] = 1;
}
$order[ $parent ][ $id ] = $position[ $parent ]++;
}
}
/* Non-AJAX way */
else
{
$order = array( \IPS\Request::i()->root ?: 'null' => \IPS\Request::i()->order );
}
/* Okay, now order */
foreach( $order as $parent => $nodes )
{
foreach ( $nodes as $id => $position )
{
/* Load Node */
try
{
if ( mb_substr( $id, 0, 2 ) === 's.' )
{
$node = call_user_func( array( $nodeClass::$subnodeClass, 'load' ), mb_substr( $id, 2 ) );
$parentColumn = $node::$parentNodeColumnId;
}
else
{
$node = $nodeClass::load( $id );
$parentColumn = $node::$databaseColumnParent;
}
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '3S101/B', 404, '' );
}
$orderColumn = $node::$databaseColumnOrder;
$idColumn = $node::$databaseColumnId;
/* Check permission */
if( !$node->canEdit() )
{
continue;
}
if( !$node::$nodeSortable or $orderColumn === NULL )
{
continue;
}
/* Do it */
if ( $parentColumn )
{
if ( is_numeric( $parent ) and $node->$idColumn == $parent AND !isset( $nodeClass::$subnodeClass ) )
{
/* It is attempting to assign a parent ID of itself which will break the tree */
$parent = $nodeClass::$databaseColumnParentRootValue;
}
$node->$parentColumn = ( $parent === 'null' ) ? 0 : is_numeric( $parent ) ? $parent : $nodeClass::$databaseColumnParentRootValue;
}
$node->$orderColumn = $position;
$node->save();
}
if( $parent !== 'null' )
{
$node = $nodeClass::load( $parent );
$node->setLastComment();
$node->setLastReview();
$node->save();
}
}
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
/* Log */
\IPS\Session::i()->log( 'acplog__node_reorder', array( $this->title => TRUE ), TRUE );
/* Allow plugins to act if necessary */
$this->_afterReorder( $order );
/* If this is an AJAX request, just respond */
if( \IPS\Request::i()->isAjax() )
{
return;
}
/* Otherwise, redirect */
else
{
\IPS\Output::i()->redirect( $this->url->setQueryString( array( 'root' => \IPS\Request::i()->root ) ) );
}
\IPS\Output::i()->sendOutput();
}
/**
* Function to execute after nodes are reordered. Do nothing by default but plugins can extend.
*
* @param array $order The new ordering that was saved
* @return void
*/
protected function _afterReorder( $order )
{
}
/**
* Permissions
*
* @return void
*/
protected function permissions()
{
/* Get node */
$nodeClass = $this->nodeClass;
if ( \IPS\Request::i()->subnode )
{
$nodeClass = $nodeClass::$subnodeClass;
}
$node = NULL;
if ( \IPS\Request::i()->id )
{
try
{
$node = $nodeClass::load( \IPS\Request::i()->id );
\IPS\Output::i()->title = $node->_title;
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2S101/M', 404, '' );
}
}
else
{
\IPS\Output::i()->error( 'node_error', '2S101/X', 404, '' );
}
/* Check permission */
if( !$node->canManagePermissions() )
{
\IPS\Output::i()->error( 'node_noperm_edit', '2S101/O', 403, '' );
}
/* Get current permissions */
try
{
$current = \IPS\Db::i()->select( '*', 'core_permission_index', array( 'app=? AND perm_type=? AND perm_type_id=?', $nodeClass::$permApp, $nodeClass::$permType, $node->_id ) )->first();
}
catch( \UnderflowException $e )
{
/* Recommended permissions */
$current = array();
foreach ( $nodeClass::$permissionMap as $k => $v )
{
switch ( $k )
{
case 'view':
case 'read':
$current["perm_{$v}"] = '*';
break;
case 'add':
case 'reply':
case 'review':
case 'upload':
case 'download':
default:
$current["perm_{$v}"] = implode( ',', array_keys( \IPS\Member\Group::groups( TRUE, FALSE ) ) );
break;
}
}
}
/* Build Matrix */
$matrix = new \IPS\Helpers\Form\Matrix;
$matrix->manageable = FALSE;
$matrix->langPrefix = $nodeClass::$permissionLangPrefix . 'perm__';
$matrix->columns = array(
'label' => function( $key, $value, $data )
{
return $value;
},
);
try
{
$disabledPermissions = $node->disabledPermissions();
}
catch( \UnderflowException $e )
{
if( $e->getCode() != 199 )
{
throw $e;
}
\IPS\Output::i()->error( 'generic_error', '4F383/1', 500, $e->getMessage() );
}
foreach ( $node->permissionTypes() as $k => $v )
{
$matrix->columns[ $k ] = function( $key, $value, $data ) use ( $current, $k, $v, $disabledPermissions )
{
$groupId = mb_substr( $key, 0, -( 2 + mb_strlen( $k ) ) );
$disabled = FALSE;
if ( array_key_exists( $groupId, $disabledPermissions ) and is_array( $disabledPermissions[ $groupId ] ) )
{
$disabled = in_array( $v, array_values( $disabledPermissions[ $groupId ] ) );
}
if ( $disabled === FALSE )
{
$disabled = ( $groupId == \IPS\Settings::i()->guest_group AND in_array( $k, array('review', 'rate' ) ) ) ? TRUE : FALSE;
}
$fieldValue = ( isset( $current[ "perm_{$v}" ] ) and ( $current[ "perm_{$v}" ] === '*' or in_array( $groupId, explode( ',', $current[ "perm_{$v}" ] ) ) ) );
return new \IPS\Helpers\Form\Checkbox( $key, ( $disabled ? 0 : $fieldValue ), NULL, array( 'disabled' => $disabled ) );
};
$matrix->checkAlls[ $k ] = ( $current[ "perm_{$v}" ] === '*' );
}
$matrix->checkAllRows = TRUE;
$rows = array();
foreach ( \IPS\Member\Group::groups() as $group )
{
$rows[ $group->g_id ] = array(
'label' => $group->name,
'view' => TRUE,
);
}
$matrix->rows = $rows;
/* Handle submissions */
if ( $values = $matrix->values() )
{
$_perms = array();
/* Check for "all" checkboxes */
foreach ( $nodeClass::$permissionMap as $k => $v )
{
if ( isset( \IPS\Request::i()->__all[ $k ] ) )
{
$_perms[ $v ] = '*';
}
else
{
$_perms[ $v ] = array();
}
}
/* Prepare insert */
$insert = array( 'app' => $nodeClass::$permApp, 'perm_type' => $nodeClass::$permType, 'perm_type_id' => $node->_id );
if ( isset( $current['perm_id'] ) )
{
$insert['perm_id'] = $current['perm_id'];
}
/* Loop groups */
foreach ( $values as $group => $perms )
{
foreach ( $nodeClass::$permissionMap as $k => $v )
{
if ( isset( $perms[ $k ] ) and $perms[ $k ] and is_array( $_perms[ $v ] ) )
{
$_perms[ $v ][] = $group;
}
}
}
/* Finalise */
foreach ( $_perms as $k => $v )
{
$insert[ "perm_{$k}" ] = is_array( $v ) ? implode( $v, ',' ) : $v;
}
/* Set the permissions */
$node->setPermissions( $insert, $matrix );
unset(\IPS\Data\Store::i()->modules);
/* Log */
\IPS\Session::i()->log( 'permissions_adjusted_node', array( $node->titleForLog() => FALSE ) );
/* Clear out member's cached "Create Menu" contents */
\IPS\Member::clearCreateMenu();
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
/* Redirect */
$this->_afterSave( NULL, $node );
return;
}
/* Display */
\IPS\Output::i()->output .= $matrix;
}
/**
* Delete
*
* @return void
*/
protected function delete()
{
/* Get node */
$nodeClass = $this->nodeClass;
if ( \IPS\Request::i()->subnode )
{
$nodeClass = $nodeClass::$subnodeClass;
}
try
{
$node = $nodeClass::load( \IPS\Request::i()->id );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2S101/J', 404, '' );
}
/* Permission check */
if( !$node->canDelete() )
{
\IPS\Output::i()->error( 'node_noperm_delete', '2S101/H', 403, '' );
}
/* Do we have any children or content? */
if ( $node->hasChildren( NULL, NULL, TRUE ) or $node->showDeleteOrMoveForm() )
{
$form = $node->deleteOrMoveForm( FALSE, TRUE );
if ( $values = $form->values() )
{
$node->deleteOrMoveFormSubmit( $values );
\IPS\Output::i()->redirect( $this->url->setQueryString( array( 'root' => ( $node->parent() ? $node->parent()->_id : '' ) ) ), 'deleted' );
}
else
{
/* Show form */
\IPS\Output::i()->output = $form;
return;
}
}
else
{
/* Make sure the user confirmed the deletion */
\IPS\Request::i()->confirmedDelete();
}
/* Delete it */
\IPS\Session::i()->log( 'acplog__node_deleted', array( $this->title => TRUE, $node->titleForLog() => FALSE ) );
$node->delete();
/* Clear out member's cached "Create Menu" contents */
\IPS\Member::clearCreateMenu();
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
/* Boink */
if( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json( "OK" );
}
else
{
\IPS\Output::i()->redirect( $this->url->setQueryString( array( 'root' => ( $node->parent() ? $node->parent()->_id : '' ) ) ), 'deleted' );
}
}
/**
* Mass Move/Delete Content
*
* @return void
*/
protected function massManageContent()
{
/* Get node */
$nodeClass = $this->nodeClass;
if ( \IPS\Request::i()->subnode )
{
$nodeClass = $nodeClass::$subnodeClass;
}
try
{
$node = $nodeClass::load( \IPS\Request::i()->id );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2S101/X', 404, '' );
}
/* Permission check */
if( !$node->canMassManageContent() )
{
\IPS\Output::i()->error( 'node_noperm_delete', '2S101/Y', 403, '' );
}
/* Is there any content? */
if ( !isset( $nodeClass::$contentItemClass ) or !$node->getContentItemCount() )
{
\IPS\Output::i()->error( 'node_mass_content_none', '2S101/Z' );
}
$contentItemClass = $nodeClass::$contentItemClass;
/* Build Wizard */
\IPS\Output::i()->title = $node->_title;
\IPS\Output::i()->output = new \IPS\Helpers\Wizard(
array(
'node_mass_content_form' => function( $data ) use( $nodeClass, $node, $contentItemClass )
{
/* Build Form */
$form = new \IPS\Helpers\Form( 'delete_node_form', 'continue', $this->url->setQueryString( array( 'do' => 'massManageContent', 'id' => $node->_id, '_step' => 'node_mass_content_form' ) ) );
$form->addHeader('node_mass_move_delete_if');
$form->add( new \IPS\Helpers\Form\Member( 'node_move_author', isset( $data['additional']['author'] ) ? array_map( function( $id ) {
return \IPS\Member::load( $id );
}, $data['additional']['author'] ) : NULL, FALSE, array( 'nullLang' => 'node_mass_move_anyone', 'multiple' => NULL ) ) );
$form->add( new \IPS\Helpers\Form\Date( 'node_move_date', isset( $data['additional']['date'] ) ? $data['additional']['date'] : 0, FALSE, array( 'unlimited' => 0, 'unlimitedLang' => 'node_mass_move_any_time' ) ) );
if ( isset( $contentItemClass::$commentClass ) )
{
$form->add( new \IPS\Helpers\Form\Number( 'node_move_comments', isset( $data['additional']['num_comments'] ) ? $data['additional']['num_comments'] : -1, FALSE, array( 'unlimited' => -1 ), NULL, NULL, NULL, 'node_move_comments_less' ) );
$form->add( new \IPS\Helpers\Form\Date( 'node_move_last_post', isset( $data['additional']['last_post'] ) ? $data['additional']['last_post'] : 0, FALSE, array( 'unlimited' => 0, 'unlimitedLang' => 'node_mass_move_any_time' ) ) );
}
if ( in_array( 'IPS\Content\Lockable', class_implements( $contentItemClass ) ) )
{
$form->add( new \IPS\Helpers\Form\Radio( 'node_move_state', isset( $data['additional']['state'] ) ? $data['additional']['state'] : 'any', FALSE, array( 'options' => array( 'locked' => 'locked', 'open' => 'unlocked', 'any' => 'node_mass_move_either' ) ) ) );
}
if ( in_array( 'IPS\Content\Pinnable', class_implements( $contentItemClass ) ) )
{
$form->add( new \IPS\Helpers\Form\Radio( 'node_move_pinned', isset( $data['additional']['pinned'] ) ? $data['additional']['pinned'] : 'any', FALSE, array( 'options' => array( '1' => 'pinned', '0' => 'node_move_pinned_not_pinned', 'any' => 'node_mass_move_either' ) ) ) );
}
if ( in_array( 'IPS\Content\Featurable', class_implements( $contentItemClass ) ) )
{
$form->add( new \IPS\Helpers\Form\Radio( 'node_move_featured', isset( $data['additional']['featured'] ) ? $data['additional']['featured'] : 'any', FALSE, array( 'options' => array( '1' => 'featured', '0' => 'node_move_pinned_not_featured', 'any' => 'node_mass_move_either' ) ) ) );
}
$form->addHeader('node_mass_move_delete_then');
$form->add( new \IPS\Helpers\Form\Node( 'node_move_content', isset( $data['moveTo'] ) ? call_user_func( array( $data['moveToClass'], 'load' ), $data['moveTo'] ) : 0, TRUE, array( 'class' => $nodeClass, 'disabled' => array( $node->_id ), 'disabledLang' => 'node_move_delete', 'zeroVal' => 'node_delete_content', 'subnodes' => FALSE, 'permissionCheck' => function( $node )
{
return array_key_exists( 'add', $node->permissionTypes() );
} ) ) );
/* Handle submissions */
if ( $values = $form->values() )
{
$data['deleteWhenDone'] = FALSE;
$data['class'] = get_class( $node );
$data['id'] = $node->_id;
if ( is_object( $values['node_move_content'] ) )
{
$data['moveToClass'] = get_class( $values['node_move_content'] );
$data['moveTo'] = $values['node_move_content']->_id;
}
else
{
unset( $data['moveToClass'] );
unset( $data['moveTo'] );
}
if ( count( $values['node_move_author'] ) )
{
$data['additional']['author'] = array_keys( $values['node_move_author'] );
}
else
{
unset( $data['additional']['author'] );
}
if ( $values['node_move_date'] )
{
$data['additional']['date'] = $values['node_move_date']->setTime( 23, 59, 59 )->getTimestamp();
}
else
{
unset( $data['additional']['date'] );
}
if ( isset( $values['node_move_comments'] ) and $values['node_move_comments'] > -1 )
{
$data['additional']['num_comments'] = $values['node_move_comments'];
}
else
{
unset( $data['additional']['num_comments'] );
}
if ( isset( $values['node_move_last_post'] ) and $values['node_move_last_post'] )
{
$data['additional']['last_post'] = $values['node_move_last_post']->setTime( 23, 59, 59 )->getTimestamp();
}
else
{
unset( $data['additional']['last_post'] );
}
if ( isset( $values['node_move_state'] ) and $values['node_move_state'] != 'any' )
{
$data['additional']['state'] = $values['node_move_state'];
}
else
{
unset( $data['additional']['state'] );
}
if ( isset( $values['node_move_pinned'] ) and $values['node_move_pinned'] != 'any' )
{
$data['additional']['pinned'] = $values['node_move_pinned'];
}
else
{
unset( $data['additional']['pinned'] );
}
if ( isset( $values['node_move_featured'] ) and $values['node_move_featured'] != 'any' )
{
$data['additional']['featured'] = $values['node_move_featured'];
}
else
{
unset( $data['additional']['featured'] );
}
return $data;
}
/* Display Form */
return (string) $form;
},
'node_mass_content_confirm' => function( $data ) use ( $node, $contentItemClass )
{
if ( isset( \IPS\Request::i()->confirm ) )
{
\IPS\Task::queue( 'core', 'DeleteOrMoveContent', $data );
if ( isset( $data['moveTo'] ) )
{
$newNode = $data['moveToClass']::load( $data['moveTo'] );
\IPS\Session::i()->log( 'acplog__node_mass_move', array( $this->title => TRUE, $node->titleForLog() => FALSE, $newNode->titleForLog() => FALSE ) );
\IPS\Output::i()->redirect( $this->url->setQueryString( array( 'root' => ( $node->parent() ? $node->parent()->_id : '' ) ) ), 'node_mass_content_moving' );
}
else
{
\IPS\Session::i()->log( 'acplog__node_mass_delete', array( $this->title => TRUE, $node->titleForLog() => FALSE ) );
\IPS\Output::i()->redirect( $this->url->setQueryString( array( 'root' => ( $node->parent() ? $node->parent()->_id : '' ) ) ), 'node_mass_content_deleting' );
}
}
else
{
$number = $node->getContentItems( NULL, NULL, $node->massMoveorDeleteWhere( $data ), TRUE );
return \IPS\Theme::i()->getTemplate( 'global', 'core' )->nodeMoveDeleteContent( $this->url->setQueryString( array( 'do' => 'massManageContent', 'id' => $node->_id ) ), \IPS\Member::loggedIn()->language()->addToStack( $contentItemClass::$title . '_pl_lc' ), $number, isset( $data['moveTo'] ) ? call_user_func( array( $data['moveToClass'], 'load' ), $data['moveTo'] ) : NULL );
}
}
),
$this->url->setQueryString( array( 'do' => 'massManageContent', 'id' => $node->_id ) )
);
}
/**
* Search
*
* @return void
*/
protected function search()
{
$rows = array();
/* Get results */
$nodeClass = $this->nodeClass;
$results = $nodeClass::search( '_title', \IPS\Request::i()->input, '_title' );
/* Get results of subnodes */
if ( isset( $nodeClass::$subnodeClass ) )
{
$subnodeClass = $nodeClass::$subnodeClass;
$results = array_merge( $results, array_values( $subnodeClass::search( '_title', \IPS\Request::i()->input, '_title' ) ) );
usort( $results, function( $a, $b ) {
return strnatcasecmp( $a->_title, $b->_title );
} );
}
/* Convert to HTML */
foreach ( $results as $result )
{
$id = ( $result instanceof $this->nodeClass ? '' : 's.' ) . $result->_id;
$rows[ $id ] = $this->_getRow( $result, FALSE, TRUE );
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'trees', 'core' )->rows( $rows, '' );
}
/**
* Allow overloading to change how the title is displayed in the tree
*
* @param $node \IPS\Node Node
* @return string
*/
protected static function nodeTitle( $node )
{
return $node->_title;
}
}