first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
@@ -0,0 +1,51 @@
<?php
/**
* @file classes/controllers/grid/ArrayGridCellProvider.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ArrayGridCellProvider
*
* @ingroup controllers_grid
*
* @brief Base class for a cell provider that can retrieve labels from arrays
*/
namespace PKP\controllers\grid;
class ArrayGridCellProvider extends GridCellProvider
{
//
// Template methods from GridCellProvider
//
/**
* This implementation assumes a simple data element array that
* has column ids as keys.
*
* @see GridCellProvider::getTemplateVarsFromRowColumn()
*
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
*
* @return array
*/
public function getTemplateVarsFromRowColumn($row, $column)
{
$element = & $row->getData();
$columnId = $column->getId();
switch ($columnId) {
case 'id':
return ['label' => $row->getId()];
default:
assert(is_array($element) && in_array($columnId, array_keys($element)));
return ['label' => $element[$columnId]];
};
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\ArrayGridCellProvider', '\ArrayGridCellProvider');
}
@@ -0,0 +1,96 @@
<?php
/**
* @file classes/controllers/grid/CategoryGridDataProvider.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class CategoryGridDataProvider
*
* @ingroup classes_controllers_grid
*
* @brief Provide access to category grid data. Can optionally use a grid data
* provider object that already provides access to data that the grid needs.
*/
namespace PKP\controllers\grid;
class CategoryGridDataProvider extends GridDataProvider
{
/** @var GridDataProvider A grid data provider that can be
* used by this category grid data provider to provide access
* to common data.
*/
public $_dataProvider;
//
// Getters and setters.
//
/**
* Get a grid data provider object.
*
* @return GridDataProvider
*/
public function getDataProvider()
{
return $this->_dataProvider;
}
/**
* Set a grid data provider object.
*
* @param GridDataProvider $dataProvider
*/
public function setDataProvider($dataProvider)
{
if ($dataProvider instanceof self) {
assert(false);
$dataProvider = null;
}
$this->_dataProvider = $dataProvider;
}
//
// Overriden methods from GridDataProvider
//
/**
* @see GridDataProvider::setAuthorizedContext()
*/
public function setAuthorizedContext(&$authorizedContext)
{
// We need to pass the authorized context object to
// the grid data provider object, if any.
$dataProvider = $this->getDataProvider();
if ($dataProvider) {
$dataProvider->setAuthorizedContext($authorizedContext);
}
parent::setAuthorizedContext($authorizedContext);
}
//
// Template methods to be implemented by subclasses
//
/**
* Retrieve the category data to load into the grid.
*
* @param \PKP\core\PKPRequest $request
* @param array|null $filter
*
* @return array
*/
public function loadCategoryData($request, $categoryDataElement, $filter = null)
{
assert(false);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\CategoryGridDataProvider', '\CategoryGridDataProvider');
}
@@ -0,0 +1,564 @@
<?php
/**
* @file classes/controllers/grid/CategoryGridHandler.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class CategoryGridHandler
*
* @ingroup controllers_grid
*
* @brief Class defining basic operations for handling HTML grids with categories.
*/
namespace PKP\controllers\grid;
use APP\template\TemplateManager;
use Illuminate\Support\LazyCollection;
use PKP\core\JSONMessage;
use PKP\core\PKPRequest;
class CategoryGridHandler extends GridHandler
{
/** @var string empty category row locale key */
public $_emptyCategoryRowText = 'grid.noItems';
/** @var array The category grid's data source. */
public $_categoryData;
/** @var ?int The category id that this grid is currently rendering. */
public $_currentCategoryId = null;
/**
* Constructor.
*
* @param null|mixed $dataProvider
*/
public function __construct($dataProvider = null)
{
parent::__construct($dataProvider);
$this->addColumn(new GridColumn(
'indent',
null,
null,
null,
new NullGridCellProvider(),
['indent' => true, 'width' => 2]
));
}
//
// Getters and setters.
//
/**
* Get the empty rows text for a category.
*
* @return string
*/
public function getEmptyCategoryRowText()
{
return $this->_emptyCategoryRowText;
}
/**
* Set the empty rows text for a category.
*
* @param string $translationKey
*/
public function setEmptyCategoryRowText($translationKey)
{
$this->_emptyCategoryRowText = $translationKey;
}
/**
* Get the category id that this grid is currently rendering.
*
* @return int
*/
public function getCurrentCategoryId()
{
return $this->_currentCategoryId;
}
/**
* Override to return the data element sequence value
* inside the passed category, if needed.
*
* @param int $categoryId The data element category id.
* @param mixed $gridDataElement The element to return the
* sequence.
*
* @return int
*/
public function getDataElementInCategorySequence($categoryId, &$gridDataElement)
{
assert(false);
}
/**
* Override to set the data element new sequence inside
* the passed category, if needed.
*
* @param int $categoryId The data element category id.
* @param mixed $gridDataElement The element to set the
* new sequence.
* @param int $newSequence The new sequence value.
*/
public function setDataElementInCategorySequence($categoryId, &$gridDataElement, $newSequence)
{
assert(false);
}
/**
* Override to define whether the data element inside the passed
* category is selected or not.
*
* @param int $categoryId
*/
public function isDataElementInCategorySelected($categoryId, &$gridDataElement)
{
assert(false);
}
/**
* Get the grid category data.
*
* @param PKPRequest $request
* @param mixed $categoryElement The category element.
*
* @return array
*/
public function &getGridCategoryDataElements($request, $categoryElement)
{
$filter = $this->getFilterSelectionData($request);
// Get the category element id.
$categories = $this->getGridDataElements($request);
$categoryElementId = array_search($categoryElement, $categories);
assert($categoryElementId !== false);
// Try to load data if it has not yet been loaded.
if (!is_array($this->_categoryData) || !array_key_exists($categoryElementId, $this->_categoryData)) {
$data = $this->loadCategoryData($request, $categoryElement, $filter);
if (is_null($data)) {
// Initialize data to an empty array.
$data = [];
}
$this->setGridCategoryDataElements($request, $categoryElementId, $data);
}
return $this->_categoryData[$categoryElementId];
}
/**
* Check whether the passed category has grid rows.
*
* @param mixed $categoryElement The category data element
* that will be checked.
* @param PKPRequest $request
*
* @return bool
*/
public function hasGridDataElementsInCategory($categoryElement, $request)
{
$data = & $this->getGridCategoryDataElements($request, $categoryElement);
assert(is_array($data));
return (bool) count($data);
}
/**
* Get the number of elements inside the passed category element.
*
* @param PKPRequest $request
*
* @return int
*/
public function getCategoryItemsCount($categoryElement, $request)
{
$data = $this->getGridCategoryDataElements($request, $categoryElement);
assert(is_array($data));
return count($data);
}
/**
* Set the grid category data.
*
* @param string $categoryElementId The category element id.
* @param mixed $data an array or ItemIterator with category elements data.
*/
public function setGridCategoryDataElements($request, $categoryElementId, $data)
{
// Make sure we have an array to store all categories elements data.
if (!is_array($this->_categoryData)) {
$this->_categoryData = [];
}
// FIXME: We go to arrays for all types of iterators because
// iterators cannot be re-used, see #6498.
if (is_array($data)) {
$this->_categoryData[$categoryElementId] = $data;
} elseif ($data instanceof \PKP\db\DAOResultFactory) {
$this->_categoryData[$categoryElementId] = $data->toAssociativeArray();
} elseif ($data instanceof \PKP\core\ItemIterator) {
$this->_categoryData[$categoryElementId] = $data->toArray();
} elseif ($data instanceof LazyCollection) {
$this->_categoryData[$categoryElementId] = iterator_to_array($data);
} else {
assert(false);
}
}
//
// Public handler methods
//
/**
* Render a category with all the rows inside of it.
*
* @param array $args
* @param PKPRequest $request
*
* @return string the serialized row JSON message or a flag
* that indicates that the row has not been found.
*/
public function fetchCategory($args, $request)
{
// Instantiate the requested row (includes a
// validity check on the row id).
$row = $this->getRequestedCategoryRow($request, $args);
$json = new JSONMessage(true);
if (is_null($row)) {
// Inform the client that the category does no longer exist.
$json->setAdditionalAttributes(['elementNotFound' => (int)$args['rowId']]);
} else {
// Render the requested category
$this->setFirstDataColumn();
$json->setContent($this->_renderCategoryInternally($request, $row));
}
return $json;
}
//
// Extended methods from GridHandler
//
/**
* @copydoc GridHandler::initialize()
*
* @param null|mixed $args
*/
public function initialize($request, $args = null)
{
parent::initialize($request, $args);
if (!is_null($request->getUserVar('rowCategoryId'))) {
$this->_currentCategoryId = (string) $request->getUserVar('rowCategoryId');
}
}
/**
* @see GridHandler::getRequestArgs()
*/
public function getRequestArgs()
{
$args = parent::getRequestArgs();
// If grid is rendering grid rows inside category,
// add current category id value so rows will also know
// their parent category.
if (!is_null($this->_currentCategoryId)) {
if ($this->getCategoryRowIdParameterName()) {
$args[$this->getCategoryRowIdParameterName()] = $this->_currentCategoryId;
}
}
return $args;
}
/**
* @copydoc GridHandler::getJSHandler()
*/
public function getJSHandler()
{
return '$.pkp.controllers.grid.CategoryGridHandler';
}
/**
* @copydoc GridHandler::setUrls()
*/
public function setUrls($request, $extraUrls = [])
{
$router = $request->getRouter();
$extraUrls['fetchCategoryUrl'] = $router->url($request, null, null, 'fetchCategory', null, $this->getRequestArgs());
parent::setUrls($request, $extraUrls);
}
/**
* @copydoc GridHandler::getRowsSequence()
*/
protected function getRowsSequence($request)
{
$categories = $this->getGridDataElements($request);
$categoryElement = $categories[$this->getCurrentCategoryId()];
return array_keys($this->getGridCategoryDataElements($request, $categoryElement));
}
/**
* @see GridHandler::doSpecificFetchGridActions()
*/
protected function doSpecificFetchGridActions($args, $request, $templateMgr)
{
// Render the body elements (category groupings + rows inside a <tbody>)
$gridBodyParts = $this->_renderCategoriesInternally($request);
$templateMgr->assign('gridBodyParts', $gridBodyParts);
}
/**
* @copydoc GridHandler::getRowDataElement()
*/
protected function getRowDataElement($request, &$rowId)
{
$rowData = parent::getRowDataElement($request, $rowId);
$rowCategoryId = $request->getUserVar('rowCategoryId');
if (is_null($rowData) && !is_null($rowCategoryId)) {
// Try to get row data inside category.
$categoryRowData = parent::getRowDataElement($request, $rowCategoryId);
if (!is_null($categoryRowData)) {
$categoryElements = $this->getGridCategoryDataElements($request, $categoryRowData);
assert(is_array($categoryElements));
if (!isset($categoryElements[$rowId])) {
return null;
}
// Let grid (and also rows) knowing the current category id.
// This value will be published by the getRequestArgs method.
$this->_currentCategoryId = $rowCategoryId;
return $categoryElements[$rowId];
}
} else {
return $rowData;
}
}
/**
* @see GridHandler::setFirstDataColumn()
*/
protected function setFirstDataColumn()
{
$columns = & $this->getColumns();
reset($columns);
// Category grids will always have indent column firstly,
// so we need to consider the first column the second one.
$secondColumn = next($columns); /** @var GridColumn $secondColumn */
$secondColumn->addFlag('firstColumn', true);
}
/**
* @see GridHandler::renderRowInternally()
*/
protected function renderRowInternally($request, $row)
{
if ($this->getCategoryRowIdParameterName()) {
$param = $this->getRequestArg($this->getCategoryRowIdParameterName());
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign('categoryId', $param);
}
return parent::renderRowInternally($request, $row);
}
/**
* Tries to identify the data element in the grids
* data source that corresponds to the requested row id.
* Raises a fatal error if such an element cannot be
* found.
*
* @param PKPRequest $request
* @param array $args
*
* @return GridRow the requested grid row, already
* configured with id and data or null if the row
* could not been found.
*/
protected function getRequestedCategoryRow($request, $args)
{
if (isset($args['rowId'])) {
// A row ID was specified. Fetch it
$elementId = $args['rowId'];
// Retrieve row data for the requested row id
// (we can use the default getRowData element, works for category grids as well).
$dataElement = $this->getRowDataElement($request, $elementId);
if (is_null($dataElement)) {
// If the row doesn't exist then
// return null. It may be that the
// row has been deleted in the meantime
// and the client does not yet know about this.
$nullVar = null;
return $nullVar;
}
}
// Instantiate a new row
return $this->_getInitializedCategoryRowInstance($request, $elementId, $dataElement);
}
//
// Protected methods to be overridden/used by subclasses
//
/**
* Get a new instance of a category grid row. May be
* overridden by subclasses if they want to
* provide a custom row definition.
*
* @return CategoryGridRow
*/
protected function getCategoryRowInstance()
{
//provide a sensible default category row definition
return new GridCategoryRow();
}
/**
* Get the category row id parameter name.
*
* @return string
*/
protected function getCategoryRowIdParameterName()
{
// Must be implemented by subclasses.
return null;
}
/**
* Implement this method to load category data into the grid.
*
* @param PKPRequest $request
* @param null|mixed $filter
*
* @return array
*/
protected function loadCategoryData($request, &$categoryDataElement, $filter = null)
{
$gridData = [];
$dataProvider = $this->getDataProvider();
if ($dataProvider instanceof \PKP\controllers\grid\CategoryGridDataProvider) {
// Populate the grid with data from the
// data provider.
$gridData = $dataProvider->loadCategoryData($request, $categoryDataElement, $filter);
}
return $gridData;
}
//
// Private helper methods
//
/**
* Instantiate a new row.
*
* @param PKPRequest $request
* @param string $elementId
*
* @return GridRow
*/
private function _getInitializedCategoryRowInstance($request, $elementId, $element)
{
// Instantiate a new row
$row = $this->getCategoryRowInstance();
$row->setGridId($this->getId());
$row->setId($elementId);
$row->setData($element);
$row->setRequestArgs($this->getRequestArgs());
// Initialize the row before we render it
$row->initialize($request);
$this->callFeaturesHook(
'getInitializedCategoryRowInstance',
['request' => $request,
'grid' => $this,
'categoryId' => $this->_currentCategoryId,
'row' => $row]
);
return $row;
}
/**
* Render all the categories internally
*
* @param PKPRequest $request
*/
private function _renderCategoriesInternally($request)
{
// Iterate through the rows and render them according
// to the row definition.
$renderedCategories = [];
$elements = $this->getGridDataElements($request);
foreach ($elements as $key => $element) {
// Instantiate a new row
$categoryRow = $this->_getInitializedCategoryRowInstance($request, $key, $element);
// Render the row
$renderedCategories[] = $this->_renderCategoryInternally($request, $categoryRow);
}
return $renderedCategories;
}
/**
* Render a category row and its data.
*
* @param PKPRequest $request
* @param GridCategoryRow $categoryRow
*
* @return string HTML for all the rows (including category)
*/
private function _renderCategoryInternally($request, $categoryRow)
{
// Prepare the template to render the category.
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign('grid', $this);
$columns = $this->getColumns();
$templateMgr->assign('columns', $columns);
$categoryDataElement = $categoryRow->getData();
$rowData = $this->getGridCategoryDataElements($request, $categoryDataElement);
// Render the data rows
$templateMgr->assign('categoryRow', $categoryRow);
// Let grid (and also rows) knowing the current category id.
// This value will be published by the getRequestArgs method.
$this->_currentCategoryId = $categoryRow->getId();
$renderedRows = $this->renderRowsInternally($request, $rowData);
$templateMgr->assign('rows', $renderedRows);
$renderedCategoryRow = $this->renderRowInternally($request, $categoryRow);
// Finished working with this category, erase the current id value.
$this->_currentCategoryId = null;
$templateMgr->assign('renderedCategoryRow', $renderedCategoryRow);
return $templateMgr->fetch('controllers/grid/gridBodyPartWithCategory.tpl');
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\CategoryGridHandler', '\CategoryGridHandler');
}
@@ -0,0 +1,42 @@
<?php
/**
* @file classes/controllers/grid/ColumnBasedGridCellProvider.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ColumnBasedGridCellProvider
*
* @ingroup controllers_grid
*
* @brief A cell provider that relies on the column implementation
* to provide cell content. Use this cell provider if you have complex
* column-specific content. If you want to provide simple labels then
* use the ArrayGridCellProvider or DataObjectGridCellProvider.
*
* @see ArrayGridCellProvider
* @see DataObjectGridCellProvider
*/
namespace PKP\controllers\grid;
class ColumnBasedGridCellProvider extends GridCellProvider
{
//
// Implement protected template methods from GridCellProvider
//
/**
* @see GridCellProvider::getTemplateVarsFromRowColumn()
*/
public function getTemplateVarsFromRowColumn($row, $column)
{
// Delegate to the column to provide template variables.
return $column->getTemplateVarsFromRow($row);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\ColumnBasedGridCellProvider', '\ColumnBasedGridCellProvider');
}
@@ -0,0 +1,93 @@
<?php
/**
* @file classes/controllers/grid/DataObjectGridCellProvider.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class DataObjectGridCellProvider
*
* @ingroup controllers_grid
*
* @brief Base class for a cell provider that can retrieve simple labels
* from DataObjects. If you need more complex cell content then you may
* be better off using a ColumnBasedGridCellProvider.
*
* @see ColumnBasedGridCellProvider
*/
namespace PKP\controllers\grid;
use PKP\facades\Locale;
class DataObjectGridCellProvider extends GridCellProvider
{
/** @var string the locale to be retrieved. */
public $_locale = null;
//
// Setters and Getters
//
/**
* Set the locale
*
* @param string $locale
*/
public function setLocale($locale)
{
$this->_locale = $locale;
}
/**
* Get the locale
*
* @return string
*/
public function getLocale()
{
if (empty($this->_locale)) {
return Locale::getLocale();
}
return $this->_locale;
}
//
// Template methods from GridCellProvider
//
/**
* This implementation assumes an element that is a
* DataObject. It will retrieve an element in the
* configured locale.
*
* @see GridCellProvider::getTemplateVarsFromRowColumn()
*
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
*
* @return array
*/
public function getTemplateVarsFromRowColumn($row, $column)
{
$element = $row->getData();
$columnId = $column->getId();
assert($element instanceof \PKP\core\DataObject && !empty($columnId));
$data = $element->getData($columnId);
// For localized fields, $data will be an array; otherwise,
// it will be a value suitable for conversion to string.
// If it's localized, fetch the value in the current locale.
if (is_array($data)) {
$data = $element->getLocalizedData($columnId);
}
return ['label' => $data];
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\DataObjectGridCellProvider', '\DataObjectGridCellProvider');
}
@@ -0,0 +1,64 @@
<?php
/**
* @file classes/controllers/grid/DateGridCellProvider.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class DateGridCellProvider
*
* @ingroup controllers_grid
*
* @brief Wraps date formatting support around a provided DataProvider.
*/
namespace PKP\controllers\grid;
use PKP\core\PKPString;
class DateGridCellProvider extends GridCellProvider
{
/** @var GridCellProvider The actual data provider to wrap */
public $_dataProvider;
/** @var string The format to use; see DateTime::format */
public $_format;
/**
* Constructor
*
* @param GridCellProvider $dataProvider The object to wrap
* @param string $format See DateTime::format
*/
public function __construct($dataProvider, $format)
{
parent::__construct();
$this->_dataProvider = $dataProvider;
$this->_format = PKPString::convertStrftimeFormat($format);
}
//
// Template methods from GridCellProvider
//
/**
* Fetch a value from the provided DataProvider (in constructor)
* and format it as a date.
*
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
*
* @return array
*/
public function getTemplateVarsFromRowColumn($row, $column)
{
$v = $this->_dataProvider->getTemplateVarsFromRowColumn($row, $column);
$v['label'] = date($this->_format, strtotime($v['label']));
return $v;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\DateGridCellProvider', '\DateGridCellProvider');
}
@@ -0,0 +1,141 @@
<?php
/**
* @file classes/controllers/grid/GridBodyElement.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class GridBodyElement
*
* @ingroup controllers_grid
*
* @brief Base class for grid body elements.
*/
namespace PKP\controllers\grid;
class GridBodyElement
{
/**
* @var string identifier of the element instance - must be unique
* among all instances within a grid.
*/
public $_id;
/**
* @var array flags that can be set by the handler to trigger layout
* options in the element or in cells inside of it.
*/
public $_flags;
/** @var GridCellProvider a cell provider for cells inside this element */
public $_cellProvider;
/**
* Constructor
*
* @param null|mixed $cellProvider
*/
public function __construct($id = '', $cellProvider = null, $flags = [])
{
$this->_id = $id;
$this->_cellProvider = $cellProvider;
$this->_flags = $flags;
}
//
// Setters/Getters
//
/**
* Get the element id
*
* @return string
*/
public function getId()
{
return $this->_id;
}
/**
* Set the element id
*
* @param string $id
*/
public function setId($id)
{
$this->_id = $id;
}
/**
* Get all layout flags
*
* @return array
*/
public function getFlags()
{
return $this->_flags;
}
/**
* Get a single layout flag
*
* @param string $flag
*/
public function getFlag($flag)
{
assert(isset($this->_flags[$flag]));
return $this->_flags[$flag];
}
/**
* Check whether a layout flag is set to true.
*
* @param string $flag
*
* @return bool
*/
public function hasFlag($flag)
{
if (!isset($this->_flags[$flag])) {
return false;
}
return (bool)$this->_flags[$flag];
}
/**
* Add a layout flag
*
* @param string $flag
* @param mixed $value optional
*/
public function addFlag($flag, $value = true)
{
$this->_flags[$flag] = $value;
}
/**
* Get the cell provider
*
* @return GridCellProvider
*/
public function getCellProvider()
{
return $this->_cellProvider;
}
/**
* Set the cell provider
*
* @param GridCellProvider $cellProvider
*/
public function setCellProvider($cellProvider)
{
$this->_cellProvider = $cellProvider;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\GridBodyElement', '\GridBodyElement');
}
@@ -0,0 +1,68 @@
<?php
/**
* @file classes/controllers/grid/GridCategoryRow.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class GridCategoryRow
*
* @ingroup controllers_grid
*
* @brief Class defining basic operations for handling the category row in a grid
*
*/
namespace PKP\controllers\grid;
class GridCategoryRow extends GridRow
{
/** @var string empty row locale key */
public $_emptyCategoryRowText = 'grid.noItems';
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
// Set a default cell provider that will get the cell template
// variables from the category grid row.
$this->setCellProvider(new GridCategoryRowCellProvider());
}
//
// Getters/Setters
//
/**
* Get the no items locale key
*/
public function getEmptyCategoryRowText()
{
return $this->_emptyCategoryRowText;
}
/**
* Set the no items locale key
*/
public function setEmptyCategoryRowText($emptyCategoryRowText)
{
$this->_emptyCategoryRowText = $emptyCategoryRowText;
}
/**
* Category rows only have one cell and one label. This is it.
*/
public function getCategoryLabel()
{
return '';
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\GridCategoryRow', '\GridCategoryRow');
}
@@ -0,0 +1,76 @@
<?php
/**
* @file classes/controllers/grid/GridCategoryRowCellProvider.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class GridCategoryRowCellProvider
*
* @ingroup controllers_grid
*
* @brief Default grid category row column's cell provider. This class will retrieve
* the template variables from the category row instance.
*/
namespace PKP\controllers\grid;
class GridCategoryRowCellProvider extends GridCellProvider
{
//
// Implemented methods from GridCellProvider.
//
/**
* @see GridCellProvider::getTemplateVarsFromRowColumn()
*/
public function getTemplateVarsFromRowColumn($row, $column)
{
// Default category rows will only have the first column
// as label columns.
if ($column->hasFlag('firstColumn')) {
return ['label' => $row->getCategoryLabel()];
} else {
return ['label' => ''];
}
}
/**
* @copydoc GridCellProvider::getCellActions()
*/
public function getCellActions($request, $row, $column, $position = GridRow::GRID_ACTION_POSITION_ROW_CLICK)
{
return $row->getActions($position);
}
/**
* @see GridCellProvider::render()
*/
public function render($request, $row, $column)
{
// Default category rows will only have the first column
// as label columns.
if ($column->hasFlag('firstColumn')) {
// Store the current column template.
$template = $column->getTemplate();
// Reset to the default column template.
$column->setTemplate('controllers/grid/gridCell.tpl');
// Render the cell.
$renderedCell = parent::render($request, $row, $column);
// Restore the original column template.
$column->setTemplate($template);
return $renderedCell;
} else {
return '';
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\GridCategoryRowCellProvider', '\GridCategoryRowCellProvider');
}
@@ -0,0 +1,117 @@
<?php
/**
* @file classes/controllers/grid/GridCellProvider.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class GridCellProvider
*
* @ingroup controllers_grid
*
* @brief Base class for a grid column's cell provider.
*
* Grid cell providers provide formatted data to grid columns.
* For general information about grids, see GridHandler.
*/
namespace PKP\controllers\grid;
use APP\core\Request;
use APP\template\TemplateManager;
use PKP\facades\Locale;
class GridCellProvider
{
/**
* Constructor
*/
public function __construct()
{
}
//
// Public methods
//
/**
* To be used by a GridRow to generate a rendered representation of
* the element for the given column.
*
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
*
* @return string the rendered representation of the element for the given column
*/
public function render($request, $row, $column)
{
$columnId = $column->getId();
assert(!empty($columnId));
// Construct a default cell id (null for "nonexistent" new rows)
$rowId = $row->getId(); // Potentially null (indicating row not backed in the DB)
$cellId = isset($rowId) ? $rowId . '-' . $columnId : null;
// Assign values extracted from the element for the cell.
$templateMgr = TemplateManager::getManager($request);
$templateVars = $this->getTemplateVarsFromRowColumn($row, $column);
foreach ($templateVars as $varName => $varValue) {
$templateMgr->assign($varName, $varValue);
}
$templateMgr->assign([
'id' => $cellId,
'column' => $column,
'actions' => $this->getCellActions($request, $row, $column),
'flags' => $column->getFlags(),
'formLocales' => Locale::getSupportedFormLocales(),
]);
$template = $column->getTemplate();
assert(!empty($template));
return $templateMgr->fetch($template);
}
//
// Protected template methods
//
/**
* Subclasses have to implement this method to extract variables
* for a given column from a data element so that they may be assigned
* to template before rendering.
*
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
*
* @return array
*/
public function getTemplateVarsFromRowColumn($row, $column)
{
return [];
}
/**
* Subclasses can override this template method to provide
* cell specific actions.
*
* NB: The default implementation delegates to the grid column for
* cell-specific actions. Another thinkable implementation would
* be row-specific actions in which case action instantiation
* should be delegated to the row.
*
* @param Request $request
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
* @param int $position GRID_ACTION_POSITION_...
*
* @return array an array of LinkAction instances
*/
public function getCellActions($request, $row, $column, $position = GridHandler::GRID_ACTION_POSITION_DEFAULT)
{
return $column->getCellActions($request, $row, $position);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\GridCellProvider', '\GridCellProvider');
}
@@ -0,0 +1,175 @@
<?php
/**
* @file classes/controllers/grid/GridColumn.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class GridColumn
*
* @ingroup controllers_grid
*
* @brief The GridColumn class represents a column within a grid. It is used to
* format the data presented in a particular column, which is provided by the
* GridRow implementation, and to handle user operations on that column (such
* as clicking a checkbox).
*
* For general information on grids, see GridHandler.
*/
namespace PKP\controllers\grid;
class GridColumn extends GridBodyElement
{
public const COLUMN_ALIGNMENT_LEFT = 'left';
public const COLUMN_ALIGNMENT_CENTER = 'center';
public const COLUMN_ALIGNMENT_RIGHT = 'right';
/** @var string the column title i18n key */
public $_title;
/** @var string the column title (translated) */
public $_titleTranslated;
/** @var string the controller template for the cells in this column */
public $_template;
/**
* Constructor
*
* @param string $id Grid column identifier
* @param string $title Locale key for grid column title
* @param string $titleTranslated Optional translated grid title
* @param string $template Optional template filename for grid column, including path
* @param GridCellProvider $cellProvider Optional grid cell provider for this column
* @param array $flags Optional set of flags for this grid column
*/
public function __construct(
$id = '',
$title = null,
$titleTranslated = null,
$template = null,
$cellProvider = null,
$flags = []
) {
// Use default template if none specified
if ($template === null) {
$template = 'controllers/grid/gridCell.tpl';
}
parent::__construct($id, $cellProvider, $flags);
$this->_title = $title;
$this->_titleTranslated = $titleTranslated;
$this->_template = $template;
}
//
// Setters/Getters
//
/**
* Get the column title
*
* @return string
*/
public function getTitle()
{
return $this->_title;
}
/**
* Set the column title (already translated)
*
* @param string $title
*/
public function setTitle($title)
{
$this->_title = $title;
}
/**
* Set the column title (already translated)
*/
public function setTitleTranslated($titleTranslated)
{
$this->_titleTranslated = $titleTranslated;
}
/**
* Get the translated column title
*
* @return string
*/
public function getLocalizedTitle()
{
if ($this->_titleTranslated) {
return $this->_titleTranslated;
}
return __($this->_title);
}
/**
* get the column's cell template
*
* @return string
*/
public function getTemplate()
{
return $this->_template;
}
/**
* set the column's cell template
*
* @param string $template
*/
public function setTemplate($template)
{
$this->_template = $template;
}
/**
* @see GridBodyElement::getCellProvider()
*/
public function getCellProvider()
{
if (is_null(parent::getCellProvider())) {
// provide a sensible default cell provider
$cellProvider = new ArrayGridCellProvider();
$this->setCellProvider($cellProvider);
}
return parent::getCellProvider();
}
/**
* Get cell actions for this column.
*
* NB: Subclasses have to override this method to
* actually provide cell-specific actions. The default
* implementation returns an empty array.
*
* @param \PKP\controllers\grid\GridRow $row The row for which actions are
* being requested.
*
* @return array An array of LinkActions for the cell.
*/
public function getCellActions($request, $row, $position = GridHandler::GRID_ACTION_POSITION_DEFAULT)
{
// The default implementation returns an empty array
return [];
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\GridColumn', '\GridColumn');
foreach ([
'COLUMN_ALIGNMENT_LEFT',
'COLUMN_ALIGNMENT_CENTER',
'COLUMN_ALIGNMENT_RIGHT',
] as $constantName) {
define($constantName, constant('\GridColumn::' . $constantName));
}
}
@@ -0,0 +1,132 @@
<?php
/**
* @file classes/controllers/grid/GridDataProvider.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class GridDataProvider
*
* @ingroup classes_controllers_grid
*
* @brief Grid data providers serve data to the grid classes for presentation
* in a grid.
*
* For general information about grids, see GridHandler.
*/
namespace PKP\controllers\grid;
use Exception;
use PKP\core\PKPRequest;
use PKP\security\authorization\PolicySet;
class GridDataProvider
{
/** @var array */
public $_authorizedContext;
/**
* Constructor
*/
public function __construct()
{
}
//
// Getters and Setters
//
/**
* Set the authorized context once it
* is established.
*
* @param array $authorizedContext
*/
public function setAuthorizedContext(&$authorizedContext)
{
$this->_authorizedContext = & $authorizedContext;
}
/**
* Retrieve an object from the authorized context
*
* @param int $assocType
*
* @return mixed will return null if the context
* for the given assoc type does not exist.
*/
public function &getAuthorizedContextObject($assocType)
{
if ($this->hasAuthorizedContextObject($assocType)) {
return $this->_authorizedContext[$assocType];
} else {
$nullVar = null;
return $nullVar;
}
}
/**
* Check whether an object already exists in the
* authorized context.
*
* @param int $assocType
*
* @return bool
*/
public function hasAuthorizedContextObject($assocType)
{
return isset($this->_authorizedContext[$assocType]);
}
//
// Template methods to be implemented by subclasses
//
/**
* Get the authorization policy.
*
* @param PKPRequest $request
* @param array $args
* @param array $roleAssignments
*
* @return PolicySet
*/
public function getAuthorizationPolicy($request, $args, $roleAssignments)
{
throw new Exception('getRequestArgs called but not implemented!');
}
/**
* Get an array with all request parameters
* necessary to uniquely identify the data
* selection of this data provider.
*
* @return array
*/
public function getRequestArgs()
{
throw new Exception('getRequestArgs called but not implemented!');
}
/**
* Retrieve the data to load into the grid.
*
* @param array $filter An optional associative array with filter data
* as returned by GridHandler::getFilterSelectionData(). If no filter
* has been selected by the user then the array will be empty.
*
* @return array
*/
public function loadData($filter = [])
{
throw new Exception('getRequestArgs called but not implemented!');
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\GridDataProvider', '\GridDataProvider');
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,245 @@
<?php
/**
* @file classes/controllers/grid/GridRow.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class GridRow
*
* @ingroup controllers_grid
*
* @brief GridRow implements a row of a Grid. See GridHandler for general
* information about grids.
*
* Each Grid is populated with data that is displayed in a series of rows. Each
* row is implemented using a GridRow, which knows how to describe the data it
* represents, and can present and manage row actions such as Edit and Delete
* operations.
*
* For general information on grids, see GridHandler.
*/
namespace PKP\controllers\grid;
use PKP\core\PKPRequest;
class GridRow extends GridBodyElement
{
public const GRID_ACTION_POSITION_ROW_CLICK = 'row-click';
public const GRID_ACTION_POSITION_ROW_LEFT = 'row-left';
/** @var array */
public $_requestArgs;
/** @var string the grid this row belongs to */
public $_gridId;
/** @var mixed the row's data source */
public $_data;
/** @var bool true if the row has been modified */
public $_isModified;
/**
* @var array row actions, the first key represents
* the position of the action in the row template,
* the second key represents the action id.
*/
public $_actions = [GridHandler::GRID_ACTION_POSITION_DEFAULT => []];
/** @var string the row template */
public $_template;
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->_isModified = false;
}
//
// Getters/Setters
//
/**
* Set the grid id
*
* @param string $gridId
*/
public function setGridId($gridId)
{
$this->_gridId = $gridId;
}
/**
* Get the grid id
*
* @return string
*/
public function getGridId()
{
return $this->_gridId;
}
/**
* Set the grid request parameters.
*
* @see GridHandler::getRequestArgs()
*
* @param array $requestArgs
*/
public function setRequestArgs($requestArgs)
{
$this->_requestArgs = $requestArgs;
}
/**
* Get the grid request parameters.
*
* @see GridHandler::getRequestArgs()
*
* @return array
*/
public function getRequestArgs()
{
return $this->_requestArgs;
}
/**
* Set the data element(s) for this controller
*
*/
public function setData(&$data)
{
$this->_data = & $data;
}
/**
* Get the data element(s) for this controller
*/
public function &getData()
{
return $this->_data;
}
/**
* Set the modified flag for the row
*
* @param bool $isModified
*/
public function setIsModified($isModified)
{
$this->_isModified = $isModified;
}
/**
* Get the modified flag for the row
*
* @return bool
*/
public function getIsModified()
{
return $this->_isModified;
}
/**
* Get whether this row has any actions or not.
*
* @return bool
*/
public function hasActions()
{
$allActions = [];
foreach ($this->_actions as $actions) {
$allActions = array_merge($allActions, $actions);
}
return !empty($allActions);
}
/**
* Get all actions for a given position within the controller
*
* @param string $position the position of the actions
*
* @return array the LinkActions for the given position
*/
public function getActions($position = GridHandler::GRID_ACTION_POSITION_DEFAULT)
{
if (!isset($this->_actions[$position])) {
return [];
}
return $this->_actions[$position];
}
/**
* Add an action
*
* @param mixed $action a single action
* @param string $position the position of the action
*/
public function addAction($action, $position = GridHandler::GRID_ACTION_POSITION_DEFAULT)
{
if (!isset($this->_actions[$position])) {
$this->_actions[$position] = [];
}
$this->_actions[$position][$action->getId()] = $action;
}
/**
* Get the row template - override base
* implementation to provide a sensible default.
*
* @return string
*/
public function getTemplate()
{
return $this->_template;
}
/**
* Set the controller template
*
* @param string $template
*/
public function setTemplate($template)
{
$this->_template = $template;
}
//
// Public methods
//
/**
* Initialize a row instance.
*
* Subclasses can override this method.
*
* @param PKPRequest $request
* @param string $template
*/
public function initialize($request, $template = null)
{
if ($template === null) {
$template = 'controllers/grid/gridRow.tpl';
}
// Set the template.
$this->setTemplate($template);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\GridRow', '\GridRow');
foreach ([
'GRID_ACTION_POSITION_ROW_CLICK',
'GRID_ACTION_POSITION_ROW_LEFT',
] as $constantName) {
define($constantName, constant('\GridRow::' . $constantName));
}
}
@@ -0,0 +1,49 @@
<?php
/**
* @file classes/controllers/grid/LiteralGridCellProvider.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class LiteralGridCellProvider
*
* @ingroup controllers_grid
*
* @brief A cell provider that passes literal data through directly.
*/
namespace PKP\controllers\grid;
class LiteralGridCellProvider extends GridCellProvider
{
//
// Template methods from GridCellProvider
//
/**
* This implementation assumes a data element that is a literal value.
* If desired, the 'id' column can be used to present the row ID.
*
* @see GridCellProvider::getTemplateVarsFromRowColumn()
*
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
*
* @return array
*/
public function getTemplateVarsFromRowColumn($row, $column)
{
switch ($column->getId()) {
case 'id':
return ['label' => $row->getId()];
case 'value':
default:
return ['label' => $row->getData()];
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\LiteralGridCellProvider', '\LiteralGridCellProvider');
}
@@ -0,0 +1,37 @@
<?php
/**
* @file classes/controllers/grid/NullGridCellProvider.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class NullGridCellProvider
*
* @ingroup controllers_grid
*
* @brief Class to return null when render method is called by a grid handler.
* Use this when you want to create a column with no content at all (for layout
* purposes using flags, for example).
*/
namespace PKP\controllers\grid;
class NullGridCellProvider extends GridCellProvider
{
//
// Template methods from GridCellProvider
//
/**
* @see GridCellProvider::render()
*/
public function render($request, $row, $column)
{
return null;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\NullGridCellProvider', '\NullGridCellProvider');
}
@@ -0,0 +1,65 @@
<?php
/**
* @file classes/controllers/grid/feature/CollapsibleGridFeature.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class CollapsibleGridFeature
*
* @ingroup controllers_grid_feature
*
* @brief Add collapse and expand functionality to grids.
*
*/
namespace PKP\controllers\grid\feature;
use APP\template\TemplateManager;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\NullAction;
class CollapsibleGridFeature extends GridFeature
{
/**
* @copydoc GridFeature::GridFeature()
* Constructor.
*/
public function __construct($id = 'collapsible')
{
parent::__construct($id);
}
/**
* @copyDoc GridFeature::getJSClass()
*/
public function getJSClass()
{
return '$.pkp.classes.features.CollapsibleGridFeature';
}
/**
* @copyDoc GridFeature::fetchUIElement()
*/
public function fetchUIElements($request, $grid)
{
$controlLink = new LinkAction(
'expandGridControlLink',
new NullAction(),
null,
'expand_all'
);
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign('controlLink', $controlLink);
$markup = $templateMgr->fetch('controllers/grid/feature/collapsibleGridFeature.tpl');
return ['collapsibleLink' => $markup];
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\feature\CollapsibleGridFeature', '\CollapsibleGridFeature');
}
@@ -0,0 +1,195 @@
<?php
/**
* @file classes/controllers/grid/feature/GeneralPagingFeature.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class GeneralPagingFeature
*
* @ingroup controllers_grid_feature
*
* @brief Base class that implements common functionality for
* paging features.
*
*/
namespace PKP\controllers\grid\feature;
use APP\core\Application;
use PKP\controllers\grid\GridHandler;
use PKP\core\ArrayItemIterator;
use PKP\core\ItemIterator;
use PKP\handler\PKPHandler;
class GeneralPagingFeature extends GridFeature
{
/** @var ItemIterator */
private $_itemIterator;
/** @var int itemsPerPage */
private $_itemsPerPage;
/**
* @see GridFeature::GridFeature()
*
* @param string $id Feature identifier.
* @param null|int $itemsPerPage Optional Number of items to show at
* the first time.
* Constructor.
*/
public function __construct($id, $itemsPerPage = null)
{
$this->_itemsPerPage = $itemsPerPage;
parent::__construct($id);
}
//
// Getters and setters.
//
/**
* Get item iterator.
*
* @return ItemIterator
*/
public function getItemIterator()
{
return $this->_itemIterator;
}
//
// Extended GridFeature methods.
//
/**
* @copydoc GridFeature::setOptions()
*/
public function setOptions($request, $grid)
{
// Get the default items per page setting value.
$rangeInfo = PKPHandler::getRangeInfo($request, $grid->getId());
$iterator = $this->getItemIterator();
$defaultItemsPerPage = $rangeInfo->getCount();
// Check for a component level items per page setting.
$componentItemsPerPage = $request->getUserVar($this->_getItemsPerPageParamName($grid->getId()));
if (!$componentItemsPerPage) {
$componentItemsPerPage = $this->_itemsPerPage;
}
if ($componentItemsPerPage) {
$currentItemsPerPage = $componentItemsPerPage;
} else {
$currentItemsPerPage = $defaultItemsPerPage;
}
$this->addOptions([
'itemsPerPageParamName' => $this->_getItemsPerPageParamName($grid->getId()),
'defaultItemsPerPage' => $defaultItemsPerPage,
'currentItemsPerPage' => $currentItemsPerPage,
'itemsTotal' => $iterator->getCount(),
'pageParamName' => PKPHandler::getPageParamName($grid->getId()),
'currentPage' => $iterator->getPage()
]);
parent::setOptions($request, $grid);
}
//
// Hooks implementation.
//
/**
* @copydoc GridFeature::gridInitialize()
* The feature will know about the current filter
* value so it can request grid refreshes keeping
* the filter.
*/
public function getGridDataElements($args)
{
$filter = $args['filter'];
if (is_array($filter) && !empty($filter)) {
$this->addOptions(['filter' => json_encode($filter)]);
}
}
/**
* @copydoc GridFeature::setGridDataElements()
*/
public function setGridDataElements($args)
{
$grid = & $args['grid'];
$data = & $args['data'];
if (is_array($data)) {
$request = Application::get()->getRequest();
$rangeInfo = $grid->getGridRangeInfo($request, $grid->getId());
$itemIterator = new ArrayItemIterator($data, $rangeInfo->getPage(), $rangeInfo->getCount());
$this->_itemIterator = $itemIterator;
$data = $itemIterator->toArray();
} elseif ($data instanceof ItemIterator) {
$this->_itemIterator = $data;
}
}
/**
* @copydoc GridFeature::getRequestArgs()
*/
public function getRequestArgs($args)
{
$grid = $args['grid'];
$requestArgs = & $args['requestArgs'];
// Add paging info so grid actions will not lose paging context.
// Only works if grid link actions use the getRequestArgs
// returned content.
$request = Application::get()->getRequest();
$rangeInfo = $grid->getGridRangeInfo($request, $grid->getId());
$requestArgs[GridHandler::getPageParamName($grid->getId())] = $rangeInfo->getPage();
$requestArgs[$this->_getItemsPerPageParamName($grid->getId())] = $rangeInfo->getCount();
}
/**
* @copydoc GridFeature::getGridRangeInfo()
*/
public function getGridRangeInfo($args)
{
$request = $args['request'];
$grid = $args['grid'];
$rangeInfo = $args['rangeInfo'];
// Add grid level items per page setting, if any.
$itemsPerPage = $request->getUserVar($this->_getItemsPerPageParamName($grid->getId()));
if ($this->_itemsPerPage) {
$itemsPerPage = $this->_itemsPerPage;
} // Feature config overrides.
if ($itemsPerPage) {
$rangeInfo->setCount($itemsPerPage);
}
}
//
// Private helper methods.
//
/**
* Get the range info items per page parameter name.
*
* @param string $rangeName
*
* @return string
*/
private function _getItemsPerPageParamName($rangeName)
{
return $rangeName . 'ItemsPerPage';
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\feature\GeneralPagingFeature', '\GeneralPagingFeature');
}
@@ -0,0 +1,301 @@
<?php
/**
* @file classes/controllers/grid/feature/GridFeature.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class GridFeature
*
* @ingroup controllers_grid_feature
*
* @brief Base grid feature class. A feature is a type of plugin specific
* to the grid widgets. It provides several hooks to allow injection of
* additional grid functionality. This class implements template methods
* to be extended by subclasses.
*
*/
namespace PKP\controllers\grid\feature;
use PKP\controllers\grid\GridHandler;
use PKP\core\PKPRequest;
class GridFeature
{
/** @var string */
public $_id;
/** @var array */
public $_options;
/**
* Constructor.
*
* @param string $id Feature id.
*/
public function __construct($id)
{
$this->setId($id);
}
//
// Getters and setters.
//
/**
* Get feature id.
*
* @return string
*/
public function getId()
{
return $this->_id;
}
/**
* Set feature id.
*
* @param string $id
*/
public function setId($id)
{
$this->_id = $id;
}
/**
* Get feature js class options.
*
* @return array
*/
public function getOptions()
{
return $this->_options;
}
/**
* Add feature js class options.
*
* @param array $options $optionId => $optionValue
*/
public function addOptions($options)
{
assert(is_array($options));
$this->_options = array_merge((array) $this->getOptions(), $options);
}
//
// Protected methods to be used or extended by subclasses.
//
/**
* Set feature js class options. Extend this method to
* define more feature js class options.
*
* @param PKPRequest $request
* @param GridHandler $grid
*/
public function setOptions($request, $grid)
{
$renderedElements = $this->fetchUIElements($request, $grid);
if ($renderedElements) {
foreach ($renderedElements as $id => $markup) {
$this->addOptions([$id => $markup]);
}
}
}
/**
* Fetch any user interface elements that
* this feature needs to add its functionality
* into the grid. Use this only for ui elements
* that grid will not fetch itself.
*
* @param PKPRequest $request
* @param GridHandler $grid The grid that this
* feature is attached to.
*
* @return array It is expected that the array
* returns data in this format:
* $elementId => $elementMarkup
*/
public function fetchUIElements($request, $grid)
{
return [];
}
/**
* Return the java script feature class.
*
* @return string|null
*/
public function getJSClass()
{
return null;
}
//
// Public hooks to be implemented in subclasses.
//
/**
* Hook called every time grid request args are
* required. Note that if the specific grid implementation
* extends the getRequestArgs method, this hook will only
* be called if the extending method call its parent.
*
* @param array $args
* 'grid' => GridHandler
* 'requestArgs' => array
*/
public function getRequestArgs($args)
{
return null;
}
/**
* Hook called every time the grid range info is
* retrieved.
*
* @param array $args
* 'request' => PKPRequest
* 'grid' => GridHandler
* 'rangeInfo' => DBResultRange
*/
public function getGridRangeInfo($args)
{
return null;
}
/**
* Hook called when grid data is retrieved.
*
* @param array $args
* 'request' => PKPRequest
* 'grid' => GridHandler
* 'gridData' => mixed (array or ItemIterator)
* 'filter' => array
*/
public function getGridDataElements($args)
{
return null;
}
/**
* Hook called before grid data is setted.
*
* @param array $args
* 'grid' => GridHandler
* 'data' => mixed (array or ItemIterator)
*/
public function setGridDataElements($args)
{
return null;
}
/**
* Hook called every time grid initialize a row object.
*
* @param array $args
* 'grid' => GridHandler,
* 'row' => GridRow
*/
public function getInitializedRowInstance($args)
{
return null;
}
/**
* Hook called on grid category row initialization.
*
* @param array $args 'request' => PKPRequest
* 'grid' => CategoryGridHandler
* 'categoryId' => int
* 'row' => GridCategoryRow
*/
public function getInitializedCategoryRowInstance($args)
{
return null;
}
/**
* Hook called on grid's initialization.
*
* @param array $args Contains the grid handler referenced object
* in 'grid' array index.
*/
public function gridInitialize($args)
{
return null;
}
/**
* Hook called on grid's data loading.
*
* @param array $args
* 'request' => PKPRequest,
* 'grid' => GridHandler,
* 'gridData' => array
*/
public function loadData($args)
{
return null;
}
/**
* Hook called on grid fetching.
*
* @param array $args 'grid' => GridHandler
*/
public function fetchGrid($args)
{
$grid = & $args['grid'];
$request = & $args['request'];
$this->setOptions($request, $grid);
}
/**
* Hook called after a group of rows is fetched.
*
* @param array $args
* 'request' => PKPRequest
* 'grid' => GridHandler
* 'jsonMessage' => JSONMessage
*/
public function fetchRows($args)
{
return null;
}
/**
* Hook called after a row is fetched.
*
* @param array $args
* 'request' => PKPRequest
* 'grid' => GridHandler
* 'row' => mixed GridRow or null
* 'jsonMessage' => JSONMessage
*/
public function fetchRow($args)
{
return null;
}
/**
* Hook called when save grid items sequence
* is requested.
*
* @param array $args 'request' => PKPRequest,
* 'grid' => GridHandler
*/
public function saveSequence($args)
{
return null;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\feature\GridFeature', '\GridFeature');
}
@@ -0,0 +1,161 @@
<?php
/**
* @file classes/controllers/grid/feature/InfiniteScrollingFeature.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class InfiniteScrollingFeature
*
* @ingroup controllers_grid_feature
*
* @brief Add infinite scrolling functionality to grids. It doesn't support
* category grids.
*
*/
namespace PKP\controllers\grid\feature;
use APP\template\TemplateManager;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\NullAction;
class InfiniteScrollingFeature extends GeneralPagingFeature
{
/**
* @copydoc GeneralPagingFeature::GeneralPagingFeature()
* Constructor.
*
* @param null|mixed $itemsPerPage
*/
public function __construct($id = 'infiniteScrolling', $itemsPerPage = null)
{
parent::__construct($id, $itemsPerPage);
}
//
// Extended methods from GridFeature.
//
/**
* @copydoc GridFeature::getJSClass()
*/
public function getJSClass()
{
return '$.pkp.classes.features.InfiniteScrollingFeature';
}
/**
* @copydoc GridFeature::fetchUIElements()
*/
public function fetchUIElements($request, $grid)
{
$options = $this->getOptions();
$shown = $options['currentItemsPerPage'] * $options['currentPage'];
if ($shown > $options['itemsTotal']) {
$shown = $options['itemsTotal'];
}
$moreItemsLinkAction = false;
if ($shown < $options['itemsTotal']) {
$moreItemsLinkAction = new LinkAction(
'moreItems',
new NullAction(),
__('grid.action.moreItems'),
'more_items'
);
}
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign([
'iterator' => $this->getItemIterator(),
'shown' => $shown,
'grid' => $grid,
'moreItemsLinkAction' => $moreItemsLinkAction,
]);
return [
'pagingMarkup' => $templateMgr->fetch('controllers/grid/feature/infiniteScrolling.tpl'),
];
}
//
// Hooks implementation.
//
/**
* @copydoc GridFeature::fetchRows()
*/
public function fetchRows($args)
{
$request = $args['request'];
$grid = $args['grid'];
$jsonMessage = $args['jsonMessage'];
// Render the paging options, including updated markup.
$this->setOptions($request, $grid);
$pagingAttributes = ['pagingInfo' => $this->getOptions()];
// Add paging attributes to json so grid can update UI.
$additionalAttributes = (array) $jsonMessage->getAdditionalAttributes();
$jsonMessage->setAdditionalAttributes(
array_merge(
$pagingAttributes,
$additionalAttributes
)
);
}
/**
* @copydoc GridFeature::fetchRow()
* Check if user really deleted a row and fetch the last one from
* the current page.
*/
public function fetchRow($args)
{
$request = $args['request'];
$grid = $args['grid'];
$row = $args['row'];
$jsonMessage = $args['jsonMessage'];
$pagingAttributes = [];
// Render the paging options, including updated markup.
$this->setOptions($request, $grid);
$pagingAttributes['pagingInfo'] = $this->getOptions();
if (is_null($row)) {
$gridData = $grid->getGridDataElements($request);
// Get the last data element id of the current page.
end($gridData);
$lastRowId = key($gridData);
// Get the row and render it.
$args = ['rowId' => $lastRowId];
$row = $grid->getRequestedRow($request, $args);
$pagingAttributes['deletedRowReplacement'] = $grid->renderRow($request, $row);
} else {
// No need for paging markup.
unset($pagingAttributes['pagingInfo']['pagingMarkup']);
}
// Add paging attributes to json so grid can update UI.
$additionalAttributes = $jsonMessage->getAdditionalAttributes();
// Unset sequence map until we support that.
unset($additionalAttributes['sequenceMap']);
$jsonMessage->setAdditionalAttributes(
array_merge(
$pagingAttributes,
$additionalAttributes
)
);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\feature\InfiniteScrollingFeature', '\InfiniteScrollingFeature');
}
@@ -0,0 +1,191 @@
<?php
/**
* @file classes/controllers/grid/feature/OrderCategoryGridItemsFeature.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class OrderCategoryGridItemsFeature
*
* @ingroup controllers_grid_feature
*
* @brief Implements category grid ordering functionality.
*
*/
namespace PKP\controllers\grid\feature;
use PKP\controllers\grid\GridHandler;
use PKP\core\PKPRequest;
class OrderCategoryGridItemsFeature extends OrderItemsFeature
{
public const ORDER_CATEGORY_GRID_CATEGORIES_ONLY = 1;
public const ORDER_CATEGORY_GRID_CATEGORIES_ROWS_ONLY = 2;
public const ORDER_CATEGORY_GRID_CATEGORIES_AND_ROWS = 3;
/**
* Constructor.
*
* @param int $typeOption Defines which grid elements will
* be orderable (categories and/or rows).
* @param bool $overrideRowTemplate This feature uses row
* actions and it will force the usage of the gridRow.tpl.
* If you want to use a different grid row template file, set this flag to
* false and make sure to use a template file that adds row actions.
* @param GridHandler $grid The grid this feature is to be part of
*/
public function __construct($typeOption = self::ORDER_CATEGORY_GRID_CATEGORIES_AND_ROWS, $overrideRowTemplate = true, $grid = null)
{
parent::__construct($overrideRowTemplate);
if ($grid) {
$grid->_constants['ORDER_CATEGORY_GRID_CATEGORIES_ONLY'] = self::ORDER_CATEGORY_GRID_CATEGORIES_ONLY;
$grid->_constants['ORDER_CATEGORY_GRID_CATEGORIES_ROWS_ONLY'] = self::ORDER_CATEGORY_GRID_CATEGORIES_ROWS_ONLY;
$grid->_constants['ORDER_CATEGORY_GRID_CATEGORIES_AND_ROWS'] = self::ORDER_CATEGORY_GRID_CATEGORIES_AND_ROWS;
}
$this->addOptions(['type' => $typeOption]);
}
//
// Getters and setters.
//
/**
* Return this feature type.
*
* @return int One of the ORDER_CATEGORY_GRID_... constants
*/
public function getType()
{
$options = $this->getOptions();
return $options['type'];
}
//
// Extended methods from GridFeature.
//
/**
* @see GridFeature::getJSClass()
*/
public function getJSClass()
{
return '$.pkp.classes.features.OrderCategoryGridItemsFeature';
}
//
// Hooks implementation.
//
/**
* @see OrderItemsFeature::getInitializedRowInstance()
*/
public function getInitializedRowInstance($args)
{
if ($this->getType() != self::ORDER_CATEGORY_GRID_CATEGORIES_ONLY) {
parent::getInitializedRowInstance($args);
}
}
/**
* @see GridFeature::getInitializedCategoryRowInstance()
*/
public function getInitializedCategoryRowInstance($args)
{
if ($this->getType() != self::ORDER_CATEGORY_GRID_CATEGORIES_ROWS_ONLY) {
$row = & $args['row'];
$this->addRowOrderAction($row);
}
}
/**
* @see GridFeature::saveSequence()
*/
public function saveSequence($args)
{
$request = & $args['request'];
$grid = & $args['grid'];
$data = json_decode($request->getUserVar('data'));
$gridCategoryElements = $grid->getGridDataElements($request);
if ($this->getType() != self::ORDER_CATEGORY_GRID_CATEGORIES_ROWS_ONLY) {
$categoriesData = [];
foreach ($data as $categoryData) {
$categoriesData[] = $categoryData->categoryId;
}
// Save categories sequence.
$firstSeqValue = $grid->getDataElementSequence(reset($gridCategoryElements));
foreach ($gridCategoryElements as $rowId => $element) {
$rowPosition = array_search($rowId, $categoriesData);
$newSequence = $firstSeqValue + $rowPosition;
$currentSequence = $grid->getDataElementSequence($element);
if ($newSequence != $currentSequence) {
$grid->setDataElementSequence($request, $rowId, $element, $newSequence);
}
}
}
// Save rows sequence, if this grid has also orderable rows inside each category.
$this->_saveRowsInCategoriesSequence($request, $grid, $gridCategoryElements, $data);
}
//
// Private helper methods.
//
/**
* Save row elements sequence inside categories.
*
* @param PKPRequest $request
* @param GridHandler $grid
* @param array $gridCategoryElements
*/
public function _saveRowsInCategoriesSequence($request, &$grid, $gridCategoryElements, $data)
{
if ($this->getType() != self::ORDER_CATEGORY_GRID_CATEGORIES_ONLY) {
foreach ($gridCategoryElements as $categoryId => $element) {
$gridRowElements = $grid->getGridCategoryDataElements($request, $element);
if (!$gridRowElements) {
continue;
}
// Get the correct rows sequence data.
/** @var ?array */
$rowsData = null;
foreach ($data as $categoryData) {
if ($categoryData->categoryId == $categoryId) {
$rowsData = $categoryData->rowsId;
break;
}
}
unset($rowsData[0]); // remove the first element, it is always the parent category ID
$gridRowElement = reset($gridRowElements);
$firstSeqValue = $grid->getDataElementInCategorySequence($categoryId, $gridRowElement);
foreach ($gridRowElements as $rowId => $element) {
$newSequence = array_search($rowId, $rowsData);
$currentSequence = $grid->getDataElementInCategorySequence($categoryId, $element);
if ($newSequence != $currentSequence) {
$grid->setDataElementInCategorySequence($categoryId, $element, $newSequence);
}
}
}
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\feature\OrderCategoryGridItemsFeature', '\OrderCategoryGridItemsFeature');
foreach ([
'ORDER_CATEGORY_GRID_CATEGORIES_ONLY',
'ORDER_CATEGORY_GRID_CATEGORIES_ROWS_ONLY',
'ORDER_CATEGORY_GRID_CATEGORIES_AND_ROWS',
] as $constantName) {
define($constantName, constant('\OrderCategoryGridItemsFeature::' . $constantName));
}
}
@@ -0,0 +1,80 @@
<?php
/**
* @file classes/controllers/grid/feature/OrderGridItemsFeature.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class OrderGridItemsFeature
*
* @ingroup controllers_grid_feature
*
* @brief Implements grid ordering functionality.
*
*/
namespace PKP\controllers\grid\feature;
class OrderGridItemsFeature extends OrderItemsFeature
{
/**
* Constructor.
*
* @copydoc OrderItemsFeature::OrderItemsFeature()
*
* @param null|mixed $nonOrderableItemsMessage
*/
public function __construct($overrideRowTemplate = true, $nonOrderableItemsMessage = null)
{
parent::__construct($overrideRowTemplate, $nonOrderableItemsMessage);
}
//
// Extended methods from GridFeature.
//
/**
* @see GridFeature::getJSClass()
*/
public function getJSClass()
{
return '$.pkp.classes.features.OrderGridItemsFeature';
}
//
// Hooks implementation.
//
/**
* @see GridFeature::saveSequence()
*
* @param array $args
*/
public function saveSequence($args)
{
$request = & $args['request'];
$grid = & $args['grid'];
$data = json_decode($request->getUserVar('data'));
$gridElements = $grid->getGridDataElements($request);
if (empty($gridElements)) {
return;
}
$firstSeqValue = $grid->getDataElementSequence(reset($gridElements));
foreach ($gridElements as $rowId => $element) {
$rowPosition = array_search($rowId, $data);
$newSequence = $firstSeqValue + $rowPosition;
$currentSequence = $grid->getDataElementSequence($element);
if ($newSequence != $currentSequence) {
$grid->setDataElementSequence($request, $rowId, $element, $newSequence);
}
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\feature\OrderGridItemsFeature', '\OrderGridItemsFeature');
}
@@ -0,0 +1,217 @@
<?php
/**
* @file classes/controllers/grid/feature/OrderItemsFeature.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class OrderItemsFeature
*
* @ingroup controllers_grid_feature
*
* @brief Base class for grid widgets ordering functionality.
*
*/
namespace PKP\controllers\grid\feature;
use APP\template\TemplateManager;
use PKP\controllers\grid\GridRow;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\NullAction;
class OrderItemsFeature extends GridFeature
{
/** @var bool */
public $_overrideRowTemplate;
/** @var string */
public $_nonOrderableItemMessage;
/**
* Constructor.
*
* @param bool $overrideRowTemplate This feature uses row
* actions and it will force the usage of the gridRow.tpl.
* If you want to use a different grid row template file, set this flag to
* false and make sure to use a template file that adds row actions.
* @param string $nonOrderableItemMessage optional A translated message to be used
* when user tries to move a non orderable grid item.
*/
public function __construct($overrideRowTemplate, $nonOrderableItemMessage = null)
{
parent::__construct('orderItems');
$this->setOverrideRowTemplate($overrideRowTemplate);
$this->setNonOrderableItemMessage($nonOrderableItemMessage);
}
//
// Getters and setters.
//
/**
* Set override row template flag.
*/
public function setOverrideRowTemplate($overrideRowTemplate)
{
$this->_overrideRowTemplate = $overrideRowTemplate;
}
/**
* Get override row template flag.
*
* @param GridRow $gridRow
*
* @return bool
*/
public function getOverrideRowTemplate(&$gridRow)
{
// Make sure we don't return the override row template
// flag to objects that are not instances of GridRow class.
if ($gridRow instanceof GridRow) {
return $this->_overrideRowTemplate;
} else {
return false;
}
}
/**
* Set non orderable item message.
*
* @param string $nonOrderableItemMessage Message already translated.
*/
public function setNonOrderableItemMessage($nonOrderableItemMessage)
{
$this->_nonOrderableItemMessage = $nonOrderableItemMessage;
}
/**
* Get non orderable item message.
*
* @return string Message already translated.
*/
public function getNonOrderableItemMessage()
{
return $this->_nonOrderableItemMessage;
}
//
// Extended methods from GridFeature.
//
/**
* @see GridFeature::setOptions()
*/
public function setOptions($request, $grid)
{
parent::setOptions($request, $grid);
$router = $request->getRouter();
$this->addOptions([
'saveItemsSequenceUrl' => $router->url($request, null, null, 'saveSequence', null, $grid->getRequestArgs()),
'csrfToken' => $request->getSession()->getCsrfToken(),
]);
}
/**
* @see GridFeature::fetchUIElements()
*/
public function fetchUIElements($request, $grid)
{
$templateMgr = TemplateManager::getManager($request);
$UIElements = [];
if ($this->isOrderActionNecessary()) {
$templateMgr->assign('gridId', $grid->getId());
$UIElements['orderFinishControls'] = $templateMgr->fetch('controllers/grid/feature/gridOrderFinishControls.tpl');
}
$nonOrderableItemMessage = $this->getNonOrderableItemMessage();
if ($nonOrderableItemMessage) {
$templateMgr->assign('orderMessage', $nonOrderableItemMessage);
$UIElements['orderMessage'] = $templateMgr->fetch('controllers/grid/feature/gridOrderNonOrderableMessage.tpl');
}
return $UIElements;
}
//
// Hooks implementation.
//
/**
* @see GridFeature::getInitializedRowInstance()
*/
public function getInitializedRowInstance($args)
{
$row = & $args['row'];
if ($args['grid']->getDataElementSequence($row->getData()) !== false) {
$this->addRowOrderAction($row);
}
}
/**
* @see GridFeature::gridInitialize()
*/
public function gridInitialize($args)
{
$grid = & $args['grid'];
if ($this->isOrderActionNecessary()) {
$grid->addAction(
new LinkAction(
'orderItems',
new NullAction(),
__('grid.action.order'),
'order_items'
)
);
}
}
//
// Protected methods.
//
/**
* Add grid row order action.
*
* @param GridRow $row
*/
public function addRowOrderAction($row)
{
if ($this->getOverrideRowTemplate($row)) {
$row->setTemplate('controllers/grid/gridRow.tpl');
}
$row->addAction(
new LinkAction(
'moveItem',
new NullAction(),
'',
'order_items'
),
GridRow::GRID_ACTION_POSITION_ROW_LEFT
);
}
//
// Protected template methods.
//
/**
* Return if this feature will use
* a grid level order action. Default is
* true, override it if needed.
*
* @return bool
*/
public function isOrderActionNecessary()
{
return true;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\feature\OrderItemsFeature', '\OrderItemsFeature');
}
@@ -0,0 +1,45 @@
<?php
/**
* @file classes/controllers/grid/feature/OrderListbuilderItemsFeature.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class OrderListbuilderItemsFeature
*
* @ingroup controllers_grid_feature
*
* @brief Implements listbuilder ordering functionality.
*
*/
namespace PKP\controllers\grid\feature;
class OrderListbuilderItemsFeature extends OrderItemsFeature
{
/**
* Constructor.
*/
public function __construct()
{
parent::__construct(false);
}
//
// Extended methods from GridFeature.
//
/**
* @see GridFeature::getJSClass()
*/
public function getJSClass()
{
return '$.pkp.classes.features.OrderListbuilderItemsFeature';
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\feature\OrderListbuilderItemsFeature', '\OrderListbuilderItemsFeature');
}
@@ -0,0 +1,165 @@
<?php
/**
* @file classes/controllers/grid/feature/PagingFeature.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PagingFeature
*
* @ingroup controllers_grid_feature
*
* @brief Add paging functionality to grids.
*
*/
namespace PKP\controllers\grid\feature;
use APP\template\TemplateManager;
class PagingFeature extends GeneralPagingFeature
{
/**
* @see GridFeature::GridFeature()
* Constructor.
*
* @param string $id Feature identifier.
*/
public function __construct($id = 'paging')
{
parent::__construct($id);
}
//
// Extended methods from GridFeature.
//
/**
* @copydoc GridFeature::getJSClass()
*/
public function getJSClass()
{
return '$.pkp.classes.features.PagingFeature';
}
/**
* @copydoc GridFeature::fetchUIElements()
*/
public function fetchUIElements($request, $grid)
{
$options = $this->getOptions();
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign([
'iterator' => $this->getItemIterator(),
'currentItemsPerPage' => $options['currentItemsPerPage'],
'grid' => $grid,
]);
return ['pagingMarkup' => $templateMgr->fetch('controllers/grid/feature/gridPaging.tpl')];
}
//
// Hooks implementation.
//
/**
* @copydoc GridFeature::fetchRow()
* Check if user really deleted a row. Handle following cases:
* 1 - recently added requested row is on previous pages and its
* addition changes the current requested page items;
* 2 - deleted a row from a page that's not the last one;
* 3 - deleted the last row from a page that's not the last one;
*
* The solution is:
* 1 - fetch the first grid data row;
* 2 - fetch the last grid data row;
* 3 - send a request to refresh the entire grid usign the previous
* page.
*/
public function fetchRow($args)
{
$request = $args['request'];
$grid = $args['grid'];
$row = $args['row'];
$jsonMessage = $args['jsonMessage'];
$pagingAttributes = [];
if (is_null($row)) {
$gridData = $grid->getGridDataElements($request);
$iterator = $this->getItemIterator();
$rangeInfo = $grid->getGridRangeInfo($request, $grid->getId());
// Check if row was really deleted or if the requested row is
// just not inside the requested range.
$deleted = true;
$topLimitRowId = (int) $request->getUserVar('topLimitRowId');
$bottomLimitRowId = (int) $request->getUserVar('bottomLimitRowId');
reset($gridData);
$firstDataId = key($gridData);
next($gridData);
$secondDataId = key($gridData);
end($gridData);
$lastDataId = key($gridData);
if ($secondDataId == $topLimitRowId) {
$deleted = false;
// Case 1.
// Row was added but it's on previous pages, so the first
// item of the grid was moved to the second place by the added
// row. Render the first one that's currently not visible yet in
// grid.
$args = ['rowId' => $firstDataId];
$row = $grid->getRequestedRow($request, $args);
$pagingAttributes['newTopRow'] = $grid->renderRow($request, $row);
}
if ($firstDataId == $topLimitRowId && $lastDataId == $bottomLimitRowId) {
$deleted = false;
}
if ($deleted) {
if ((empty($gridData) ||
// When DAOResultFactory, it seems that if no items were found for the current
// range information, the last page is fetched, which give us grid data even if
// the current page is empty. So we check for iterator and rangeInfo current pages.
$iterator->getPage() != $rangeInfo->getPage())
&& $iterator->getPageCount() >= 1) {
// Case 3.
$pagingAttributes['loadLastPage'] = true;
} else {
if (count($gridData) >= $rangeInfo->getCount()) {
// Case 2.
// Get the last data element id of the current page.
end($gridData);
$firstRowId = key($gridData);
// Get the row and render it.
$args = ['rowId' => $firstRowId];
$row = $grid->getRequestedRow($request, $args);
$pagingAttributes['deletedRowReplacement'] = $grid->renderRow($request, $row);
}
}
}
}
// Render the paging options, including updated markup.
$this->setOptions($request, $grid);
$pagingAttributes['pagingInfo'] = $this->getOptions();
// Add paging attributes to json so grid can update UI.
$additionalAttributes = $jsonMessage->getAdditionalAttributes();
$jsonMessage->setAdditionalAttributes(
array_merge(
$pagingAttributes,
$additionalAttributes
)
);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\feature\PagingFeature', '\PagingFeature');
}
@@ -0,0 +1,85 @@
<?php
/**
* @file classes/controllers/grid/feature/selectableItems/ItemSelectionGridColumn.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ItemSelectionGridColumn
*
* @ingroup classes_controllers_grid_feature_selectableItems
*
* @brief Implements a column with checkboxes to select grid items.
*/
namespace PKP\controllers\grid\feature\selectableItems;
use PKP\controllers\grid\ColumnBasedGridCellProvider;
use PKP\controllers\grid\GridColumn;
class ItemSelectionGridColumn extends GridColumn
{
/** @var string */
public $_selectName;
/**
* Constructor
*
* @param string $selectName The name of the form parameter
* to which the selected files will be posted.
*/
public function __construct($selectName)
{
assert(is_string($selectName) && !empty($selectName));
$this->_selectName = $selectName;
$cellProvider = new ColumnBasedGridCellProvider();
parent::__construct(
'select',
'common.select',
null,
'controllers/grid/gridRowSelectInput.tpl',
$cellProvider,
['width' => 3]
);
}
//
// Getters and Setters
//
/**
* Get the select name.
*
* @return string
*/
public function getSelectName()
{
return $this->_selectName;
}
//
// Public methods
//
/**
* Method expected by ColumnBasedGridCellProvider
* to render a cell in this column.
*
* @see ColumnBasedGridCellProvider::getTemplateVarsFromRowColumn()
*/
public function getTemplateVarsFromRow($row)
{
// Return the data expected by the column's cell template.
return [
'elementId' => $row->getId(),
'selectName' => $this->getSelectName(),
'selected' => $row->getFlag('selected')];
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\feature\selectableItems\ItemSelectionGridColumn', '\ItemSelectionGridColumn');
}
@@ -0,0 +1,68 @@
<?php
/**
* @file classes/controllers/grid/feature/selectableItems/SelectableItemsFeature.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class SelectableItemsFeature
*
* @ingroup controllers_grid_feature_selectableItems
*
* @brief Implements grid widgets selectable items functionality.
*
*/
namespace PKP\controllers\grid\feature\selectableItems;
use PKP\controllers\grid\feature\GridFeature;
class SelectableItemsFeature extends GridFeature
{
/**
* Constructor.
*/
public function __construct()
{
parent::__construct('selectableItems');
}
//
// Hooks implementation.
//
/**
* @see GridFeature::gridInitialize()
*/
public function gridInitialize($args)
{
$grid = $args['grid'];
// Add checkbox column to the grid.
$grid->addColumn(new ItemSelectionGridColumn($grid->getSelectName()));
}
/**
* @see GridFeature::getInitializedRowInstance()
*/
public function getInitializedRowInstance($args)
{
/** @var \PKP\controllers\grid\CategoryGridHandler|\PKP\controllers\grid\GridHandler */
$grid = $args['grid'];
/** @var \PKP\controllers\grid\GridRow */
$row = $args['row'];
if ($grid instanceof \PKP\controllers\grid\CategoryGridHandler) {
$categoryId = $grid->getCurrentCategoryId();
$row->addFlag('selected', (bool) $grid->isDataElementInCategorySelected($categoryId, $row->getData()));
} else {
$row->addFlag('selected', (bool) $grid->isDataElementSelected($row->getData()));
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\feature\selectableItems\SelectableItemsFeature', '\SelectableItemsFeature');
}
@@ -0,0 +1,217 @@
<?php
/**
* @file classes/controllers/grid/files/FilesGridCapabilities.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class FilesGridCapabilities
*
* @ingroup classes_controllers_grid_files
*
* @brief Defines files grid capabilities. Should be used by grid handlers
* that handle submission files to store which capabilities the grid has.
*/
namespace PKP\controllers\grid\files;
use PKP\controllers\grid\files\fileList\linkAction\DownloadAllLinkAction;
use PKP\core\PKPRequest;
use PKP\file\FileArchive;
use PKP\linkAction\LinkAction;
class FilesGridCapabilities
{
// Define the grid capabilities.
public const FILE_GRID_ADD = 0x00000001;
public const FILE_GRID_DOWNLOAD_ALL = 0x00000002;
public const FILE_GRID_DELETE = 0x00000004;
public const FILE_GRID_VIEW_NOTES = 0x00000008;
public const FILE_GRID_MANAGE = 0x00000010;
public const FILE_GRID_EDIT = 0x00000020;
/** @var bool */
public $_canAdd;
/** @var bool */
public $_canViewNotes;
/** @var bool */
public $_canDownloadAll;
/** @var bool */
public $_canDelete;
/** @var bool */
public $_canManage;
/** @var bool */
public $_canEdit;
/**
* Constructor
*
* @param int $capabilities A bit map with zero or more
* FILE_GRID_* capabilities set.
*/
public function __construct($capabilities = 0)
{
$this->setCanAdd($capabilities & self::FILE_GRID_ADD);
$this->setCanDownloadAll($capabilities & self::FILE_GRID_DOWNLOAD_ALL);
$this->setCanDelete($capabilities & self::FILE_GRID_DELETE);
$this->setCanViewNotes($capabilities & self::FILE_GRID_VIEW_NOTES);
$this->setCanManage($capabilities & self::FILE_GRID_MANAGE);
$this->setCanEdit($capabilities & self::FILE_GRID_EDIT);
}
//
// Getters and Setters
//
/**
* Does this grid allow the addition of files or revisions?
*
* @return bool
*/
public function canAdd()
{
return $this->_canAdd;
}
/**
* Set whether or not the grid allows the addition of files or revisions.
*
* @param bool $canAdd
*/
public function setCanAdd($canAdd)
{
$this->_canAdd = (bool) $canAdd;
}
/**
* Does this grid allow viewing of notes?
*
* @return bool
*/
public function canViewNotes()
{
return $this->_canViewNotes;
}
/**
* Set whether this grid allows viewing of notes or not.
*/
public function setCanViewNotes($canViewNotes)
{
$this->_canViewNotes = $canViewNotes;
}
/**
* Can the user download all files as an archive?
*
* @return bool
*/
public function canDownloadAll()
{
return $this->_canDownloadAll && FileArchive::isFunctional();
}
/**
* Set whether user can download all files as an archive or not.
*/
public function setCanDownloadAll($canDownloadAll)
{
$this->_canDownloadAll = $canDownloadAll;
}
/**
* Can the user delete files from this grid?
*
* @return bool
*/
public function canDelete()
{
return $this->_canDelete;
}
/**
* Set whether or not the user can delete files from this grid.
*
* @param bool $canDelete
*/
public function setCanDelete($canDelete)
{
$this->_canDelete = (bool) $canDelete;
}
/**
* Whether the grid allows file management (select existing files to add to grid)
*
* @return bool
*/
public function canManage()
{
return $this->_canManage;
}
/**
* Set whether the grid allows file management (select existing files to add to grid)
*/
public function setCanManage($canManage)
{
$this->_canManage = $canManage;
}
/**
* Whether the grid allows file metadata editing
*
* @return bool
*/
public function canEdit()
{
return $this->_canEdit;
}
/**
* Set whether the grid allows file metadata editing
*/
public function setCanEdit($canEdit)
{
$this->_canEdit = $canEdit;
}
/**
* Get the download all link action.
*
* @param PKPRequest $request
* @param array $files The files to be downloaded.
* @param array $linkParams The link action request
* parameters.
*
* @return ?LinkAction
*/
public function getDownloadAllAction($request, $files, $linkParams)
{
if (sizeof($files) > 0) {
return new DownloadAllLinkAction($request, $linkParams);
} else {
return null;
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\files\FilesGridCapabilities', '\FilesGridCapabilities');
foreach ([
'FILE_GRID_ADD',
'FILE_GRID_DOWNLOAD_ALL',
'FILE_GRID_DELETE',
'FILE_GRID_VIEW_NOTES',
'FILE_GRID_MANAGE',
'FILE_GRID_EDIT',
] as $constantName) {
define($constantName, constant('\FilesGridCapabilities::' . $constantName));
}
}
@@ -0,0 +1,476 @@
<?php
/**
* @file controllers/grid/plugins/PluginGridHandler.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PluginGridHandler
*
* @ingroup controllers_grid_plugins
*
* @brief Handle plugins grid requests.
*/
namespace PKP\controllers\grid\plugins;
use APP\notification\NotificationManager;
use Exception;
use PKP\controllers\grid\CategoryGridHandler;
use PKP\controllers\grid\GridColumn;
use PKP\controllers\grid\GridHandler;
use PKP\controllers\grid\plugins\form\UploadPluginForm;
use PKP\core\Core;
use PKP\core\JSONMessage;
use PKP\core\PKPApplication;
use PKP\core\PKPRequest;
use PKP\db\DAORegistry;
use PKP\file\FileManager;
use PKP\file\TemporaryFileManager;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\AjaxModal;
use PKP\notification\PKPNotification;
use PKP\plugins\Plugin;
use PKP\plugins\PluginHelper;
use PKP\plugins\PluginRegistry;
use PKP\security\Role;
use PKP\site\Version;
use PKP\site\VersionCheck;
use PKP\site\VersionDAO;
abstract class PluginGridHandler extends CategoryGridHandler
{
/**
* Constructor
*
* @param array $roles
*/
public function __construct($roles)
{
$this->addRoleAssignment(
$roles,
['enable', 'disable', 'manage', 'fetchGrid', 'fetchCategory', 'fetchRow']
);
$this->addRoleAssignment(
Role::ROLE_ID_SITE_ADMIN,
['uploadPlugin', 'upgradePlugin', 'deletePlugin', 'saveUploadPlugin', 'uploadPluginFile']
);
parent::__construct();
}
//
// Overridden template methods
//
/**
* @copydoc GridHandler::initialize()
*
* @param null|mixed $args
*/
public function initialize($request, $args = null)
{
parent::initialize($request, $args);
// Basic grid configuration
$this->setTitle('common.plugins');
// Set the no items row text
$this->setEmptyRowText('grid.noItems');
// Columns
$pluginCellProvider = new PluginGridCellProvider();
$this->addColumn(
new GridColumn(
'name',
'common.name',
null,
null,
$pluginCellProvider,
[
'showTotalItemsNumber' => true,
'collapseAllColumnsInCategories' => true
]
)
);
$descriptionColumn = new GridColumn(
'description',
'common.description',
null,
null,
$pluginCellProvider
);
$descriptionColumn->addFlag('html', true);
$this->addColumn($descriptionColumn);
$this->addColumn(
new GridColumn(
'enabled',
'common.enabled',
null,
'controllers/grid/common/cell/selectStatusCell.tpl',
$pluginCellProvider
)
);
$router = $request->getRouter();
// Grid level actions.
$userRoles = $this->getAuthorizedContextObject(PKPApplication::ASSOC_TYPE_USER_ROLES);
if (in_array(Role::ROLE_ID_SITE_ADMIN, $userRoles)) {
// Install plugin.
$this->addAction(
new LinkAction(
'upload',
new AjaxModal(
$router->url($request, null, null, 'uploadPlugin'),
__('manager.plugins.upload'),
'modal_add_file'
),
__('manager.plugins.upload'),
'add'
)
);
}
}
/**
* @copydoc GridHandler::getFilterForm()
*/
protected function getFilterForm()
{
return 'controllers/grid/plugins/pluginGridFilter.tpl';
}
/**
* @copydoc GridHandler::getFilterSelectionData()
*/
public function getFilterSelectionData($request)
{
$category = $request->getUserVar('category');
$pluginName = $request->getUserVar('pluginName');
if (is_null($category)) {
$category = PluginGalleryGridHandler::PLUGIN_GALLERY_ALL_CATEGORY_SEARCH_VALUE;
}
return ['category' => $category, 'pluginName' => $pluginName];
}
/**
* @copydoc GridHandler::renderFilter()
*/
public function renderFilter($request, $filterData = [])
{
$categoriesSymbolic = $this->loadData($request, null);
$categories = [PluginGalleryGridHandler::PLUGIN_GALLERY_ALL_CATEGORY_SEARCH_VALUE => __('grid.plugin.allCategories')];
foreach ($categoriesSymbolic as $category) {
$categories[$category] = __("plugins.categories.{$category}");
}
$filterData['categories'] = $categories;
return parent::renderFilter($request, $filterData);
}
/**
* @copydoc CategoryGridHandler::getCategoryRowInstance()
*/
protected function getCategoryRowInstance()
{
return new PluginCategoryGridRow();
}
/**
* @copydoc CategoryGridHandler::loadCategoryData()
*
* @param null|mixed $filter
*/
public function loadCategoryData($request, &$categoryDataElement, $filter = null)
{
$plugins = PluginRegistry::loadCategory($categoryDataElement);
$versionDao = DAORegistry::getDAO('VersionDAO'); /** @var VersionDAO $versionDao */
$fileManager = new FileManager();
$notHiddenPlugins = [];
foreach ((array) $plugins as $plugin) {
if (!$plugin->getHideManagement()) {
$notHiddenPlugins[$plugin->getName()] = $plugin;
}
$version = $plugin->getCurrentVersion();
if ($version == null) { // this plugin is on the file system, but not installed.
$versionFile = $plugin->getPluginPath() . '/version.xml';
if ($fileManager->fileExists($versionFile)) {
$versionInfo = VersionCheck::parseVersionXML($versionFile);
$pluginVersion = $versionInfo['version'];
} else {
$pluginVersion = new Version(
1,
0,
0,
0, // Major, minor, revision, build
Core::getCurrentDate(), // Date installed
1, // Current
'plugins.' . $plugin->getCategory(), // Type
basename($plugin->getPluginPath()), // Product
'', // Class name
0, // Lazy load
$plugin->isSitePlugin() // Site wide
);
}
$versionDao->insertVersion($pluginVersion, true);
}
}
if (!is_null($filter) && isset($filter['pluginName']) && $filter['pluginName'] != '') {
// Find all plugins that have the filter name string in their display names.
$filteredPlugins = [];
foreach ($notHiddenPlugins as $plugin) { /** @var Plugin $plugin */
$pluginName = $plugin->getDisplayName();
if (stristr($pluginName, $filter['pluginName']) !== false) {
$filteredPlugins[$plugin->getName()] = $plugin;
}
}
ksort($filteredPlugins);
return $filteredPlugins;
}
ksort($notHiddenPlugins);
return $notHiddenPlugins;
}
/**
* @copydoc CategoryGridHandler::getCategoryRowIdParameterName()
*/
public function getCategoryRowIdParameterName()
{
return 'category';
}
/**
* @copydoc GridHandler::loadData()
*/
protected function loadData($request, $filter)
{
$categories = PluginRegistry::getCategories();
if (is_array($filter) && isset($filter['category']) && in_array($filter['category'], $categories)) {
return [$filter['category'] => $filter['category']];
} else {
return array_combine($categories, $categories);
}
}
//
// Public handler methods.
//
/**
* Manage a plugin.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function manage($args, $request)
{
$plugin = $this->getAuthorizedContextObject(PKPApplication::ASSOC_TYPE_PLUGIN); /** @var Plugin $plugin */
return $plugin->manage($args, $request);
}
/**
* Enable a plugin.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function enable($args, $request)
{
$plugin = $this->getAuthorizedContextObject(PKPApplication::ASSOC_TYPE_PLUGIN); /** @var Plugin $plugin */
if ($request->checkCSRF() && $plugin->getCanEnable()) {
$plugin->setEnabled(true);
if (empty($args['disableNotification'])) {
$user = $request->getUser();
$notificationManager = new NotificationManager();
$notificationManager->createTrivialNotification($user->getId(), PKPNotification::NOTIFICATION_TYPE_PLUGIN_ENABLED, ['pluginName' => $plugin->getDisplayName()]);
}
return \PKP\db\DAO::getDataChangedEvent($request->getUserVar('plugin'), $request->getUserVar($this->getCategoryRowIdParameterName()));
}
return new JSONMessage(false);
}
/**
* Disable a plugin.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function disable($args, $request)
{
$plugin = $this->getAuthorizedContextObject(PKPApplication::ASSOC_TYPE_PLUGIN); /** @var Plugin $plugin */
if ($request->checkCSRF() && $plugin->getCanDisable()) {
$plugin->setEnabled(false);
if (empty($args['disableNotification'])) {
$user = $request->getUser();
$notificationManager = new NotificationManager();
$notificationManager->createTrivialNotification($user->getId(), PKPNotification::NOTIFICATION_TYPE_PLUGIN_DISABLED, ['pluginName' => $plugin->getDisplayName()]);
}
return \PKP\db\DAO::getDataChangedEvent($request->getUserVar('plugin'), $request->getUserVar($this->getCategoryRowIdParameterName()));
}
return new JSONMessage(false);
}
/**
* Show upload plugin form to upload a new plugin.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage
*/
public function uploadPlugin($args, $request)
{
return $this->_showUploadPluginForm(PluginHelper::PLUGIN_ACTION_UPLOAD, $request);
}
/**
* Show upload plugin form to update an existing plugin.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage
*/
public function upgradePlugin($args, $request)
{
return $this->_showUploadPluginForm(PluginHelper::PLUGIN_ACTION_UPGRADE, $request);
}
/**
* Upload a plugin file.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function uploadPluginFile($args, $request)
{
$temporaryFileManager = new TemporaryFileManager();
$user = $request->getUser();
// Return the temporary file id.
if ($temporaryFile = $temporaryFileManager->handleUpload('uploadedFile', $user->getId())) {
$json = new JSONMessage(true);
$json->setAdditionalAttributes([
'temporaryFileId' => $temporaryFile->getId()
]);
return $json;
} else {
return new JSONMessage(false, __('manager.plugins.uploadError'));
}
}
/**
* Save upload plugin file form.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function saveUploadPlugin($args, $request)
{
if (!$request->checkCSRF()) {
throw new Exception('CSRF mismatch!');
}
$function = $request->getUserVar('function');
$uploadPluginForm = new UploadPluginForm($function);
$uploadPluginForm->readInputData();
if ($uploadPluginForm->validate()) {
if ($uploadPluginForm->execute()) {
return \PKP\db\DAO::getDataChangedEvent();
}
}
return new JSONMessage(false);
}
/**
* Delete plugin.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function deletePlugin($args, $request)
{
if (!$request->checkCSRF()) {
return new JSONMessage(false);
}
$plugin = $this->getAuthorizedContextObject(PKPApplication::ASSOC_TYPE_PLUGIN);
$category = $plugin->getCategory();
$productName = basename($plugin->getPluginPath());
$versionDao = DAORegistry::getDAO('VersionDAO'); /** @var VersionDAO $versionDao */
$installedPlugin = $versionDao->getCurrentVersion('plugins.' . $category, $productName);
$notificationMgr = new NotificationManager();
$user = $request->getUser();
$pluginName = ['pluginName' => $plugin->getDisplayName()];
if ($installedPlugin) {
$pluginDest = Core::getBaseDir() . "/plugins/{$category}/{$productName}";
$pluginLibDest = Core::getBaseDir() . '/' . PKP_LIB_PATH . "/plugins/{$category}/{$productName}";
// make sure plugin type is valid and then delete the files
if (in_array($category, PluginRegistry::getCategories())) {
// Delete the plugin from the file system.
$fileManager = new FileManager();
$fileManager->rmtree($pluginDest);
$fileManager->rmtree($pluginLibDest);
}
if (is_dir($pluginDest) || is_dir($pluginLibDest)) {
$notificationMgr->createTrivialNotification($user->getId(), PKPNotification::NOTIFICATION_TYPE_ERROR, ['contents' => __('manager.plugins.deleteError', $pluginName)]);
} else {
$versionDao->disableVersion('plugins.' . $category, $productName);
$notificationMgr->createTrivialNotification($user->getId(), PKPNotification::NOTIFICATION_TYPE_SUCCESS, ['contents' => __('manager.plugins.deleteSuccess', $pluginName)]);
}
} else {
$notificationMgr->createTrivialNotification($user->getId(), PKPNotification::NOTIFICATION_TYPE_ERROR, ['contents' => __('manager.plugins.doesNotExist', $pluginName)]);
}
return \PKP\db\DAO::getDataChangedEvent($plugin->getName());
}
/**
* Fetch upload plugin form.
*
* @param string $function
* @param PKPRequest $request Request object
*
* @return JSONMessage JSON object
*/
public function _showUploadPluginForm($function, $request)
{
$uploadPluginForm = new UploadPluginForm($function);
$uploadPluginForm->initData();
return new JSONMessage(true, $uploadPluginForm->fetch($request));
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\grid\plugins\PluginGridHandler', '\PluginGridHandler');
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,58 @@
<?php
/**
* @file classes/controllers/listbuilder/ListbuilderGridColumn.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ListbuilderGridColumn
*
* @ingroup controllers_listbuilder
*
* @brief Represents a column within a listbuilder.
*/
namespace PKP\controllers\listbuilder;
use PKP\controllers\grid\GridColumn;
use PKP\controllers\listbuilder\users\UserListbuilderGridCellProvider;
class ListbuilderGridColumn extends GridColumn
{
/**
* Constructor
*
* @param ListbuilderHandler $listbuilder The listbuilder handler this column belongs to.
* @param string $id The optional symbolic ID for this column.
* @param string $title The optional title for this column.
* @param string $titleTranslated The optional translated title for this column.
* @param string $template The optional overridden template for this column.
* @param UserListbuilderGridCellProvider $cellProvider The optional overridden grid cell provider.
* @param array $flags Optional set of flags for this column's display.
*/
public function __construct(
$listbuilder,
$id = '',
$title = null,
$titleTranslated = null,
$template = null,
$cellProvider = null,
$flags = []
) {
// Set this here so that callers using later optional parameters don't need to
// duplicate it.
if ($template === null) {
$template = 'controllers/listbuilder/listbuilderGridCell.tpl';
}
// Make the listbuilder's source type available to the cell template as a flag
$flags['sourceType'] = $listbuilder->getSourceType();
parent::__construct($id, $title, $titleTranslated, $template, $cellProvider, $flags);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\listbuilder\ListbuilderGridColumn', '\ListbuilderGridColumn');
}
@@ -0,0 +1,88 @@
<?php
/**
* @file classes/controllers/listbuilder/ListbuilderGridRow.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ListbuilderGridRow
*
* @ingroup controllers_listbuilder
*
* @brief Handle list builder row requests.
*/
namespace PKP\controllers\listbuilder;
use PKP\controllers\grid\GridRow;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\NullAction;
class ListbuilderGridRow extends GridRow
{
/** @var bool */
public $_hasDeleteItemLink;
/**
* Constructor
*
* @param bool $hasDeleteItemLink
*/
public function __construct($hasDeleteItemLink = true)
{
parent::__construct();
$this->setHasDeleteItemLink($hasDeleteItemLink);
}
/**
* Add a delete item link action or not.
*
* @param bool $hasDeleteItemLink
*/
public function setHasDeleteItemLink($hasDeleteItemLink)
{
$this->_hasDeleteItemLink = $hasDeleteItemLink;
}
//
// Overridden template methods
//
/**
* @copydoc GridRow::initialize()
*/
public function initialize($request, $template = 'controllers/listbuilder/listbuilderGridRow.tpl')
{
parent::initialize($request);
// Set listbuilder row template
$this->setTemplate($template);
if ($this->_hasDeleteItemLink) {
// Add deletion action (handled in JS-land)
$this->addAction(
new LinkAction(
'delete',
new NullAction(),
'',
'remove_item'
)
);
}
}
/**
* @see GridRow::addAction()
*/
public function addAction($action, $position = GridRow::GRID_ACTION_POSITION_ROW_LEFT)
{
return parent::addAction($action, $position);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\listbuilder\ListbuilderGridRow', '\ListbuilderGridRow');
}
@@ -0,0 +1,417 @@
<?php
/**
* @file classes/controllers/listbuilder/ListbuilderHandler.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ListbuilderHandler
*
* @ingroup controllers_listbuilder
*
* @brief Class defining basic operations for handling Listbuilder UI elements
*/
namespace PKP\controllers\listbuilder;
use APP\core\Request;
use APP\template\TemplateManager;
use PKP\controllers\grid\GridHandler;
use PKP\core\JSONMessage;
use PKP\core\PKPRequest;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\LinkActionRequest;
use PKP\linkAction\request\NullAction;
class ListbuilderHandler extends GridHandler
{
/** @var int Listbuilder source types: text-based, pulldown, ... */
public const LISTBUILDER_SOURCE_TYPE_TEXT = 0;
public const LISTBUILDER_SOURCE_TYPE_SELECT = 1;
/** @var int Listbuilder save types */
public const LISTBUILDER_SAVE_TYPE_EXTERNAL = 0;
public const LISTBUILDER_SAVE_TYPE_INTERNAL = 1;
/** @var string String to identify optgroup in the returning options data. If you want to use
* optgroup in listbuilder select, return the options data in a multidimensional array
* array[columnIndex][optgroupId][selectItemId] and also with
* array[columnIndex][LISTBUILDER_OPTGROUP_LABEL][optgroupId] */
public const LISTBUILDER_OPTGROUP_LABEL = 'optGroupLabel';
/** @var int Definition of the type of source LISTBUILDER_SOURCE_TYPE_... */
public $_sourceType;
/** @var int Constant indicating the save approach for the LB LISTBUILDER_SAVE_TYPE_... */
public $_saveType = self::LISTBUILDER_SAVE_TYPE_INTERNAL;
/** @var string Field for LISTBUILDER_SAVE_TYPE_EXTERNAL naming the field used to send the saved contents of the LB */
public $_saveFieldName = null;
/**
* @copydoc GridHandler::initialize
*
* @param null|mixed $args
*/
public function initialize($request, $args = null)
{
parent::initialize($request, $args);
if ($this->canAddItems()) {
$this->addAction($this->getAddItemLinkAction(new NullAction()));
}
}
//
// Getters and Setters
//
/**
* Get the listbuilder template.
*
* @return string
*/
public function getTemplate()
{
if (is_null($this->_template)) {
$this->setTemplate('controllers/listbuilder/listbuilder.tpl');
}
return $this->_template;
}
/**
* Set the type of source (Free text input, select from list, autocomplete)
*
* @param int $sourceType LISTBUILDER_SOURCE_TYPE_...
*/
public function setSourceType($sourceType)
{
$this->_sourceType = $sourceType;
}
/**
* Get the type of source (Free text input, select from list, autocomplete)
*
* @return int LISTBUILDER_SOURCE_TYPE_...
*/
public function getSourceType()
{
return $this->_sourceType;
}
/**
* Set the save type (using this handler or another external one)
*/
public function setSaveType($saveType)
{
$this->_saveType = $saveType;
}
/**
* Get the save type (using this handler or another external one)
*
* @return int LISTBUILDER_SAVE_TYPE_...
*/
public function getSaveType()
{
return $this->_saveType;
}
/**
* Set the save field name for LISTBUILDER_SAVE_TYPE_EXTERNAL
*
* @param string $fieldName
*/
public function setSaveFieldName($fieldName)
{
$this->_saveFieldName = $fieldName;
}
/**
* Set the save field name for LISTBUILDER_SAVE_TYPE_EXTERNAL.
* This will be the HTML field that receives the data upon submission.
*
* @return string
*/
public function getSaveFieldName()
{
assert(isset($this->_saveFieldName));
return $this->_saveFieldName;
}
/**
* Get the "add item" link action.
*
* @param LinkActionRequest $actionRequest
*
* @return LinkAction
*/
public function getAddItemLinkAction($actionRequest)
{
return new LinkAction(
'addItem',
$actionRequest,
__('grid.action.addItem'),
'add_item'
);
}
/**
* Get the new row ID from the request. For multi-column listbuilders,
* this is an array representing the row. For single-column
* listbuilders, this is a single piece of data (i.e. a string or int)
*
* @param PKPRequest $request
*/
public function getNewRowId($request)
{
return $request->getUserVar('newRowId');
}
/**
* Delete an entry.
*
* @param Request $request object
* @param mixed $rowId ID of row to modify
*
* @return bool
*/
public function deleteEntry($request, $rowId)
{
fatalError('ABSTRACT METHOD');
}
/**
* Persist an update to an entry.
*
* @param Request $request object
* @param mixed $rowId ID of row to modify
* @param mixed $newRowId ID of the new entry
*
* @return bool
*/
public function updateEntry($request, $rowId, $newRowId)
{
// This may well be overridden by a subclass to modify
// an existing entry, e.g. to maintain referential integrity.
// If not, we can simply delete and insert.
if (!$this->deleteEntry($request, $rowId)) {
return false;
}
return $this->insertEntry($request, $newRowId);
}
/**
* Persist a new entry insert.
*
* @param Request $request object
* @param mixed $newRowId ID of row to modify
*
* @return bool
*/
public function insertEntry($request, $newRowId)
{
fatalError('ABSTRACT METHOD');
}
/**
* Fetch the options for a LISTBUILDER_SOURCE_TYPE_SELECT LB
* Should return a multidimensional array:
* array(
* array('column 1 option 1', 'column 2 option 1'),
* array('column 1 option 2', 'column 2 option 2'
* );
*
* @param Request $request
*
* @return array
*/
public function getOptions($request)
{
return [];
}
//
// Publicly (remotely) available listbuilder functions
//
/**
* Fetch the listbuilder.
*
* @param array $args
* @param PKPRequest $request
*/
public function fetch($args, $request)
{
$templateMgr = TemplateManager::getManager($request);
$options = $this->getOptions($request);
$availableOptions = false;
if (is_array($options) && !empty($options)) {
$firstColumnOptions = current($options);
$optionsCount = count($firstColumnOptions);
if (is_array(current($firstColumnOptions))) { // Options with opt group, count only the selectable options.
unset($firstColumnOptions[self::LISTBUILDER_OPTGROUP_LABEL]);
$optionsCount--;
$optionsCount = count($firstColumnOptions, COUNT_RECURSIVE) - $optionsCount;
}
$listElements = $this->getGridDataElements($request);
if (count($listElements) < $optionsCount) {
$availableOptions = true;
}
}
$templateMgr->assign('availableOptions', $availableOptions);
return $this->fetchGrid($args, $request);
}
/**
* Unpack data to save using an external handler.
*
* @param string $data (the json encoded data from the listbuilder itself)
* @param array $deletionCallback callback to be used for each deleted element
* @param array $insertionCallback callback to be used for each updated element
* @param array $updateCallback callback to be used for each updated element
*/
public static function unpack($request, $data, $deletionCallback, $insertionCallback, $updateCallback)
{
$data = json_decode($data);
$status = true;
// Handle deletions
if (isset($data->deletions) && $data->deletions !== '') {
foreach (explode(' ', trim($data->deletions)) as $rowId) {
if (!call_user_func($deletionCallback, $request, $rowId, $data->numberOfRows)) {
$status = false;
}
}
}
// Handle changes and insertions
if (isset($data->changes)) {
foreach ($data->changes as $entry) {
// Get the row ID, if any, from submitted data
if (isset($entry->rowId)) {
$rowId = $entry->rowId;
unset($entry->rowId);
} else {
$rowId = null;
}
// $entry should now contain only submitted modified or new rows.
// Go through each and unpack the data in prep for application.
$changes = [];
foreach ($entry as $key => $value) {
// Match the column name and localization data, if any.
if (!preg_match('/^newRowId\[([a-zA-Z]+)\](\[([a-z][a-z](_[A-Z][A-Z])?(@([A-Za-z0-9]{5,8}|\d[A-Za-z0-9]{3}))?)\])?$/', $key, $matches)) {
assert(false);
}
// Get the column name
$column = $matches[1];
// If this is a multilingual input, fetch $locale; otherwise null
$locale = $matches[3] ?? null;
if ($locale) {
$changes[$column][$locale] = $value;
} else {
$changes[$column] = $value;
}
}
// $changes should now contain e.g.:
// array ('localizedColumnName' => array('en' => 'englishValue'),
// 'nonLocalizedColumnName' => 'someNonLocalizedValue');
if (is_null($rowId)) {
if (!call_user_func($insertionCallback, $request, $changes)) {
$status = false;
}
} else {
if (!call_user_func($updateCallback, $request, $rowId, $changes)) {
$status = false;
}
}
}
}
return $status;
}
/**
* Save the listbuilder using the internal handler.
*
* @param array $args
* @param PKPRequest $request
*/
public function save($args, $request)
{
// The ListbuilderHandler will post a list of changed
// data in the "data" post var. Need to go through it
// and reconcile the data against this list, adding/
// updating/deleting as needed.
$data = $request->getUserVar('data');
self::unpack(
$request,
$data,
[$this, 'deleteEntry'],
[$this, 'insertEntry'],
[$this, 'updateEntry']
);
}
/**
* Load the set of options for a select list type listbuilder.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function fetchOptions($args, $request)
{
$options = $this->getOptions($request);
return new JSONMessage(true, $options);
}
/**
* Can items be added to this list builder?
*
* @return bool
*/
public function canAddItems()
{
return true;
}
//
// Overridden methods from GridHandler
//
/**
* @see GridHandler::getRowInstance()
*
* @return ListbuilderGridRow
*/
protected function getRowInstance()
{
// Return a citation row
return new ListbuilderGridRow();
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\listbuilder\ListbuilderHandler', '\ListbuilderHandler');
foreach ([
'LISTBUILDER_SOURCE_TYPE_TEXT',
'LISTBUILDER_SOURCE_TYPE_SELECT',
'LISTBUILDER_SAVE_TYPE_EXTERNAL',
'LISTBUILDER_SAVE_TYPE_INTERNAL',
'LISTBUILDER_OPTGROUP_LABEL',
] as $constantName) {
define($constantName, constant('\ListbuilderHandler::' . $constantName));
}
}
@@ -0,0 +1,60 @@
<?php
/**
* @file classes/controllers/listbuilder/MultilingualListbuilderGridColumn.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class MultilingualListbuilderGridColumn
*
* @ingroup controllers_listbuilder
*
* @brief Represents a multilingual text column within a listbuilder.
*/
namespace PKP\controllers\listbuilder;
use PKP\facades\Locale;
class MultilingualListbuilderGridColumn extends ListbuilderGridColumn
{
/**
* Constructor
*
* @param null|mixed $title
* @param null|mixed $titleTranslated
* @param null|mixed $template
* @param null|mixed $cellProvider
* @param null|mixed $availableLocales
*/
public function __construct(
$listbuilder,
$id = '',
$title = null,
$titleTranslated = null,
$template = null,
$cellProvider = null,
$availableLocales = null,
$flags = []
) {
// Make sure this is a text input
assert($listbuilder->getSourceType() == ListbuilderHandler::LISTBUILDER_SOURCE_TYPE_TEXT);
// Provide a default set of available locales if not specified
if (!$availableLocales) {
$availableLocales = Locale::getSupportedFormLocales();
}
// Set some flags for multilingual support
$flags['multilingual'] = true; // This is a multilingual column.
$flags['availableLocales'] = $availableLocales; // Provide available locales
parent::__construct($listbuilder, $id, $title, $titleTranslated, $template, $cellProvider, $flags);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\listbuilder\MultilingualListbuilderGridColumn', '\MultilingualListbuilderGridColumn');
}
@@ -0,0 +1,126 @@
<?php
/**
* @file controllers/tab/workflow/PKPReviewRoundTabHandler.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPReviewRoundTabHandler
*
* @ingroup controllers_tab_workflow
*
* @brief Handle AJAX operations for review round tabs on review stages workflow pages.
*/
namespace PKP\controllers\tab\workflow;
use APP\core\Application;
use APP\handler\Handler;
use APP\notification\Notification;
use APP\template\TemplateManager;
use PKP\core\JSONMessage;
use PKP\core\PKPRequest;
use PKP\db\DAORegistry;
use PKP\notification\PKPNotification;
use PKP\security\authorization\internal\ReviewRoundRequiredPolicy;
use PKP\security\Role;
use PKP\submission\reviewRound\ReviewRoundDAO;
class PKPReviewRoundTabHandler extends Handler
{
//
// Extended methods from Handler
//
/**
* @copydoc PKPHandler::authorize()
*/
public function authorize($request, &$args, $roleAssignments)
{
// We need a review round id in request.
$this->addPolicy(new ReviewRoundRequiredPolicy($request, $args));
return parent::authorize($request, $args, $roleAssignments);
}
/**
* JSON fetch the external review round info (tab).
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function externalReviewRound($args, $request)
{
return $this->_reviewRound($args, $request);
}
/**
* @see PKPHandler::setupTemplate
*/
public function setupTemplate($request)
{
parent::setupTemplate($request);
}
//
// Protected helper methods.
//
/**
* Internal function to handle both internal and external reviews round info (tab content).
*
* @param PKPRequest $request
* @param array $args
*
* @return JSONMessage JSON object
*/
protected function _reviewRound($args, $request)
{
$this->setupTemplate($request);
// Retrieve the authorized submission, stage id and review round.
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$stageId = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE);
$reviewRound = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_REVIEW_ROUND);
// Is this round the most recent round?
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
$lastReviewRound = $reviewRoundDao->getLastReviewRoundBySubmissionId($submission->getId(), $stageId);
// Add the round information to the template.
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign('stageId', $stageId);
$templateMgr->assign('reviewRoundId', $reviewRound->getId());
$templateMgr->assign('isLastReviewRound', $reviewRound->getId() == $lastReviewRound->getId());
$templateMgr->assign('submission', $submission);
// Assign editor decision actions to the template, only if
// user is accessing the last review round for this stage.
$notificationRequestOptions = [
Notification::NOTIFICATION_LEVEL_NORMAL => [
PKPNotification::NOTIFICATION_TYPE_REVIEW_ROUND_STATUS => [Application::ASSOC_TYPE_REVIEW_ROUND, $reviewRound->getId()]],
Notification::NOTIFICATION_LEVEL_TRIVIAL => [],
];
$templateMgr->assign('reviewRoundNotificationRequestOptions', $notificationRequestOptions);
// If a user is also assigned as an author to this submission, they
// shouldn't see any editorial actions
$userAccessibleStages = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);
foreach ($userAccessibleStages as $accessibleStageId => $roles) {
if (in_array(Role::ROLE_ID_AUTHOR, $roles)) {
$templateMgr->assign('isAssignedAsAuthor', true);
break;
}
}
return $templateMgr->fetchJson('workflow/reviewRound.tpl');
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\controllers\tab\workflow\PKPReviewRoundTabHandler', '\PKPReviewRoundTabHandler');
}