Commit a065d8fa authored by rjsmelo's avatar rjsmelo

[NEW] Enable the definition of Custom Routes based on static values, alias to...

[NEW] Enable the definition of Custom Routes based on static values, alias to objects, regex or tracker fields
parent 02604888
......@@ -1970,8 +1970,10 @@ installer/schema/20170702_wiki_url_scheme_pref_default_tiki.php -text
installer/schema/20170717_add_missing_trackeritem_attachment_backlinks_tiki.php -text
installer/schema/20170726_remove_article_hashes_tiki.sql -text
installer/schema/20170726_tabular_config_tiki.sql -text
installer/schema/20170829_custom_routes_tiki.sql -text
installer/schema/20170920_admin_webservices_menu_option_tiki.sql -text
installer/schema/20170925_add_general_to_language_tiki.sql -text
installer/schema/20170930_custom_routes_menu_tiki.sql -text
installer/schema/20171002_queue_handler_size_increase_tiki.sql -text
installer/schema/20171010_action_log_info_new_column.sql -text
installer/schema/20171016_no_homepage_specificed_tiki.php -text
......
......@@ -22,6 +22,7 @@
<service id="tiki.controller.connect_server" class="Services_Connect_Server"/>
<service id="tiki.controller.connect" class="Services_Connect_Client"/>
<service id="tiki.controller.contenttemplate" class="Services_ContentTemplate_Controller"/>
<service id="tiki.controller.customroute" class="Services_CustomRoute_Controller"/>
<service id="tiki.controller.draw" class="Services_Draw_Controller"/>
<service id="tiki.controller.edit" class="Services_Edit_Controller"/>
<service id="tiki.controller.favorite" class="Services_User_FavoriteController"/>
......
......@@ -79,6 +79,7 @@
<service id="tiki.lib.css" class="cssLib">
<file>%kernel.root_dir%/lib/csslib.php</file>
</service>
<service id="tiki.lib.custom_route" class="Tiki\CustomRoute\CustomRouteLib" />
<service id="tiki.lib.dcs" class="DCSLib">
<file>%kernel.root_dir%/lib/dcs/dcslib.php</file>
</service>
......
CREATE TABLE `tiki_custom_route` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`description` varchar(255) NULL,
`type` varchar(255) NOT NULL,
`from` varchar(255) NOT NULL,
`redirect` text NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT '1',
`user_id` int(11) NOT NULL
) ENGINE=MyISAM;
INSERT INTO tiki_menu_options (menuId,type,name,url,position,section,perm,groupname) VALUES (42,'o','Custom Routes','tiki-admin_routes.php',1290,'feature_sefurl_routes','tiki_p_admin','');
<?php
// (c) Copyright 2002-2016 by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
namespace Tiki\CustomRoute;
use \Feedback;
use \TikiLib;
/**
* Custom Route controller
*/
class Controller
{
/**
* Populate a custom route item from the request
*
* @param array $request
* @return Item
*/
public function populateFromRequest($request)
{
$id = ! empty($request['route']) ? $request['route'] : '';
$type = isset($request['router_type']) ? $request['router_type'] : '';
$from = isset($request['router_from']) ? $request['router_from'] : '';
$description = isset($request['router_description']) ? $request['router_description'] : '';
$active = empty($request['router_active']) ? 0 : 1;
$params = [];
if (! empty($type)) {
$className = 'Tiki\\CustomRoute\\Type\\' . $type;
if (! class_exists($className)) {
Feedback::error(tr('An error occurred; please contact the administrator.'), 'session');
$this->redirectToAdmin();
}
/** @var Type $class */
$class = new $className();
$params = $class->parseParams($request);
}
return new Item($type, $from, $params, $description, $active, $id);
}
/**
* Handle the saving the item
*
* @param array $request
* @return array
*/
public function saveRequest($request)
{
$item = $this->populateFromRequest($request);
$errors = $item->validate();
if (empty($errors)) {
$id = $item->id;
$item->save();
$feedback = $id ? tr('Route was updated.') : tr('Route was created.');
Feedback::success($feedback, 'session');
$this->redirectToAdmin();
}
Feedback::error(['mes' => $errors], 'session');
return $item->toArray();
}
/**
* Redirect to the Custom Route admin page
*/
private function redirectToAdmin()
{
TikiLib::lib('access')->redirect('tiki-admin_routes.php');
die;
}
}
<?php
// (c) Copyright 2002-2016 by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
namespace Tiki\CustomRoute;
use TikiLib;
/**
* Custom Route
*/
class CustomRoute
{
/**
* Checks if the $path matches a custom route
* If so, the user is redirected to the "go to" destination.
*
* @param $path
*/
public static function match($path)
{
$access = TikiLib::lib('access');
$routeLib = TikiLib::lib('custom_route');
$routes = $routeLib->getRouteByType(null, ['type' => 'asc']);
foreach ($routes as $row) {
$route = new Item($row['type'], $row['from'], $row['redirect'], $row['active'], $row['id']);
if ($redirect = $route->getRedirectPath($path)) {
$access->redirect($redirect);
};
}
}
}
<?php
// (c) Copyright 2002-2017 by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
namespace Tiki\CustomRoute;
use TikiLib;
/**
* Class CustomRouteLib
*/
class CustomRouteLib extends TikiLib
{
protected $tableName = 'tiki_custom_route';
/**
* List all routes or fetch one route
*
* @param null $routeId
* @return mixed
*/
public function getRoute($routeId = null)
{
$routeTable = $this->table($this->tableName);
$conditions = [];
if ($routeId) {
$conditions['id'] = $routeId;
return $routeTable->fetchRow([], $conditions);
}
return $routeTable->fetchAll([], $conditions);
}
/**
* Insert or Update a route
* @param $type
* @param $from
* @param $redirect
* @param $description
* @param $active
* @param $id
*/
public function setRoute($type, $from, $redirect, $description, $active, $id)
{
$values = [
'type' => $type,
'from' => $from,
'redirect' => $redirect,
'description' => $description,
'active' => $active
];
$routeTable = $this->table($this->tableName);
if (! $id) {
$routeTable->insert($values);
} else {
$routeTable->update($values, ['id' => $id]);
}
}
/**
* Remove the route
*
* @param $routeId
*/
public function removeRoute($routeId)
{
$routeTable = $this->table($this->tableName);
$routeTable->delete(['id' => $routeId]);
$logslib = TikiLib::lib('logs');
$logslib->add_action('Removed', $routeId, 'custom_route');
}
public function getTikiPath($path)
{
$routeTable = $this->table($this->tableName);
$conditions = [];
$conditions['sef_url'] = $path;
return $routeTable->fetchRow(['tiki_url'], $conditions);
}
/**
* Get the available routes for a specific type
*
* @param array $type
* @param array $orderClause
* @param int $onlyActive
* @return mixed
*/
public function getRouteByType($type = [], $orderClause = [], $onlyActive = 1)
{
$routeTable = $this->table($this->tableName);
$conditions = [];
if (! empty($type)) {
$conditions['type'] = $routeTable->in($type);
}
if ($onlyActive) {
$conditions['active'] = 1;
}
return $routeTable->fetchAll([], $conditions, -1, -1, $orderClause);
}
/**
* Checks if there is a router already defined
*
* @param $from
* @param $id
*
* @return bool
* True router is already defined, false otherwise.
*/
public function checkRouteExists($from, $id = null)
{
$routeTable = $this->table($this->tableName);
$conditions = [
'from' => $from,
];
if (! empty($id)) {
$conditions['id'] = $routeTable->not($id);
}
$duplicateId = $routeTable->fetchOne('id', $conditions);
return $duplicateId !== false;
}
}
<?php
// (c) Copyright 2002-2017 by authors of the Tiki Wiki/CMS/Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
namespace Tiki\CustomRoute;
use \TikiLib;
/**
* Custom route item
*/
class Item
{
public $id;
public $type;
public $from;
public $redirect;
public $description;
public $active;
/**
* Item constructor.
*
* @param $type
* @param $from
* @param $redirect
* @param $description
* @param int $active
* @param null $id
*/
public function __construct($type, $from, $redirect, $description, $active = 1, $id = null)
{
$this->type = $type;
$this->from = $from;
$this->redirect = is_array($redirect) ? json_encode($redirect) : $redirect;
$this->description = $description;
$this->active = $active;
$this->id = $id;
}
/**
* Save item in database
*/
public function save()
{
/** @var \CustomRouteLib $routeLib */
$routeLib = TikiLib::lib('custom_route');
$routeLib->setRoute($this->type, $this->from, $this->redirect, $this->description, $this->active, $this->id);
}
/**
* Load a custom route by ID
*
* @param $id
* @return array|null|Item
*/
public static function load($id)
{
/** @var \CustomRouteLib $routeLib */
$routeLib = TikiLib::lib('custom_route');
$details = $routeLib->getRoute($id);
if (empty($details)) {
return null;
}
return new self(
$details['type'],
$details['from'],
$details['redirect'],
$details['description'],
$details['active'],
$details['id']
);
}
/**
* Check if a given path matches a custom route
*
* @param $path
* @return bool|string
*/
public function getRedirectPath($path)
{
global $tikilib;
switch ($this->type) {
case 'Direct':
if ($path === $this->from) {
$redirectDetails = json_decode($this->redirect, true);
return $redirectDetails['to'];
}
break;
case 'Object':
if ($path === $this->from) {
$redirectDetails = json_decode($this->redirect, true);
$type = $redirectDetails['type'];
$objectId = $redirectDetails['object'];
if ($type == 'wiki page') {
$pageName = $tikilib->get_page_name_from_id($objectId);
$pageSlug = TikiLib::lib('wiki')->get_slug_by_page($pageName);
if (empty($pageSlug)) {
return false;
}
$objectId = $pageSlug;
}
require_once('tiki-sefurl.php');
$smarty = TikiLib::lib('smarty');
$smarty->loadPlugin('smarty_modifier_sefurl');
$url = smarty_modifier_sefurl($objectId, $type);
return $url;
}
break;
case 'TrackerField':
$fromRegex = '|' . $this->from . '|';
preg_match($fromRegex, $path, $matches);
if (empty($matches[1])) {
return false;
}
$redirectDetails = json_decode($this->redirect, true);
$trklib = TikiLib::lib('trk');
$itemId = $trklib->get_item_id(
$redirectDetails['tracker'],
$redirectDetails['tracker_field'],
$matches[1]
);
if (empty($itemId)) {
return false;
}
return 'item' . $itemId;
break;
default:
break;
}
return false;
}
/**
* Validate the route requirements are met.
*
* @return array
*/
public function validate()
{
$errors = [];
if (empty($this->from)) {
$errors[] = tr('From is required');
}
$routeLib = TikiLib::lib('custom_route');
if ($routeLib->checkRouteExists($this->from, $this->id)) {
$errors[] = tr('There is a route with the same From path already defined.');
}
if (empty($this->type)) {
$errors[] = tr('Type is required');
}
/** @var Type $class */
$className = 'Tiki\\CustomRoute\\Type\\' . $this->type;
if (class_exists($className)) {
$class = new $className();
$params = json_decode($this->redirect, true);
$errors += $class->validateParams($params);
} else {
$errors[] = tr('Selected type is not supported');
}
return $errors;
}
/**
* Converts the Item object into a array
*
* @return array
*/
public function toArray()
{
return [
'id' => $this->id,
'type' => $this->type,
'from' => $this->from,
'params' => json_decode($this->redirect, true),
'description' => $this->description,
'active' => $this->active,
];
}
}
<?php
// (c) Copyright 2002-2017 by authors of the Tiki Wiki/CMS/Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
namespace Tiki\CustomRoute;
/**
* Abstract class for custom route types
*/
abstract class Type
{
protected $errorMessage;
/**
* Get the param definitions required for the route
* @return array
*/
abstract protected function getParams();
/**
* Get route type name
* @return mixed
*/
public function getRouteType()
{
return (new \ReflectionClass($this))->getShortName();
}
/**
* Parse the type specific params sent in the custom route form
*
* @param $routeParams
* @return string
*/
public function parseParams(array $routeParams)
{
$params = [];
$inputParams = $this->getParams();
$taskName = strtolower($this->getRouteType());
foreach ($inputParams as $key => $input) {
$inputName = $taskName . '_' . $key;
$params[$key] = $routeParams[$inputName];
}
return json_encode($params);
}
/**
* Checks for errors in the required fields
*
* @param array $params
* @return array
*/
public function validateParams(array $params)
{
$errors = [];
$inputParams = $this->getParams();
foreach ($inputParams as $key => $input) {
if (empty($input['required'])) {
continue;
}
if (empty($params[$key])) {
$errors[] = sprintf(tr('%s is required'), $input['name']);
}
}
return $errors;
}
}
<?php
// (c) Copyright 2002-2017 by authors of the Tiki Wiki/CMS/Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
namespace Tiki\CustomRoute\Type;
use Tiki\CustomRoute\Type;
/**
* Custom route for direct routing transformation
*/
class Direct extends Type
{
/**
* @inheritdoc
*/
public function getParams()
{
return [
'to' => [
'name' => tr('To'),
'type' => 'text',
'required' => true,
],
];
}
}
<?php
// (c) Copyright 2002-2017 by authors of the Tiki Wiki/CMS/Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
namespace Tiki\CustomRoute\Type;
use \TikiLib;
use Tiki\CustomRoute\Type;
/**
* Custom route for objects
*/
class Object extends Type
{
/**
* @inheritdoc
*/
public function getParams()
{
return [
'type' => [
'name' => tr('Type'),
'type' => 'select',
'required' => true,
'options' => [
'' => '',
'article' => tr('Article'),
'blog' => tr('Blog'),
'forum' => tr('Forum'),
'gallery' => tr('Image Gallery'),
'wiki page' => tr('Wiki Page'),
],
],
'object' => [
'name' => tr('Object'),
'type' => 'select',
'required' => true,
'function' => 'getObjectsByType',
'args' => ['type'],
],
];
}
/**
* Retrieve the list the available objects for a specific type
*
* @param $type
* @return array
*/
public function getObjectsByType($type)
{
$tikilib = new TikiLib;
$objects = [];
switch ($type) {
case 'article':
$articles = TikiLib::lib('art')->list_articles(0, -1, 'title_asc');
foreach ($articles['data'] as $article) {
$objects[$article['articleId']] = $article['title'];
}
break;
case 'blog':
$blogs = TikiLib::lib('blog')->list_blogs(0, -1, 'title_asc');
foreach ($blogs['data'] as $blog) {
$objects[$blog['blogId']] = $blog['title'];
}
break;
case 'forum':
$forums = TikiLib::lib('comments')->list_forums(0, -1, 'name_asc');
foreach ($forums['data'] as $forum) {
$objects[$forum['forumId']] = $forum['name'];
}
break;
case 'gallery':
$galleries = $tikilib->list_galleries(0, -1, 'name_desc');
foreach ($galleries['data'] as $gallery) {
$objects[$gallery['galleryId']] = $gallery['name'];
}
break;
case 'wiki page':
$pages = $tikilib->list_pages(0, -1, 'pageName_asc');
foreach ($pages['data'] as $page) {
$objects[$page['page_id']] = $page['pageName'];
}