看板初始化提交
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Application Helper
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class AppHelper extends Base
|
||||
{
|
||||
public function tooltipMarkdown($markdownText, $icon = 'fa-info-circle')
|
||||
{
|
||||
return '<span class="tooltip"><i class="fa '.$icon.'"></i><script type="text/template"><div class="markdown">'.$this->helper->text->markdown($markdownText).'</div></script></span>';
|
||||
}
|
||||
|
||||
public function tooltipHTML($htmlText, $icon = 'fa-info-circle')
|
||||
{
|
||||
return '<span class="tooltip"><i class="fa '.$icon.'"></i><script type="text/template"><div class="markdown">'.$htmlText.'</div></script></span>';
|
||||
}
|
||||
|
||||
public function tooltipLink($label, $link)
|
||||
{
|
||||
return '<span class="tooltip" data-href="'.$link.'">'.$label.'</span>';
|
||||
}
|
||||
|
||||
public function getToken()
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
public function isAjax()
|
||||
{
|
||||
return $this->request->isAjax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Javascript component
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
public function component($name, array $params = array())
|
||||
{
|
||||
return '<div class="js-'.$name.'" data-params=\''.json_encode($params, JSON_HEX_APOS).'\'></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get config variable
|
||||
*
|
||||
* @access public
|
||||
* @param string $param
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function config($param, $default = '')
|
||||
{
|
||||
return $this->configModel->get($param, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sidebar menu active
|
||||
*
|
||||
* @access public
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @param string $plugin
|
||||
* @return string
|
||||
*/
|
||||
public function checkMenuSelection($controller, $action = '', $plugin = '')
|
||||
{
|
||||
$result = strtolower($this->getRouterController()) === strtolower($controller);
|
||||
|
||||
if ($result && $action !== '') {
|
||||
$result = strtolower($this->getRouterAction()) === strtolower($action);
|
||||
}
|
||||
|
||||
if ($result && $plugin !== '') {
|
||||
$result = strtolower($this->getPluginName()) === strtolower($plugin);
|
||||
}
|
||||
|
||||
return $result ? 'class="active"' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin name from route
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getPluginName()
|
||||
{
|
||||
return $this->router->getPlugin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get router controller
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getRouterController()
|
||||
{
|
||||
return $this->router->getController();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get router action
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getRouterAction()
|
||||
{
|
||||
return $this->router->getAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get javascript language code
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function jsLang()
|
||||
{
|
||||
return $this->languageModel->getJsLanguageCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current language requires RTL direction
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function isRtlLanguage()
|
||||
{
|
||||
return $this->languageModel->isRtlLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date format for Jquery DatePicker
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getJsDateFormat()
|
||||
{
|
||||
$format = $this->dateParser->getUserDateFormat();
|
||||
$format = str_replace('m', 'mm', $format);
|
||||
$format = str_replace('Y', 'yy', $format);
|
||||
$format = str_replace('d', 'dd', $format);
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get time format for Jquery Plugin DateTimePicker
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getJsTimeFormat()
|
||||
{
|
||||
$format = $this->dateParser->getUserTimeFormat();
|
||||
$format = str_replace('H', 'HH', $format);
|
||||
$format = str_replace('i', 'mm', $format);
|
||||
$format = str_replace('g', 'h', $format);
|
||||
$format = str_replace('a', 'tt', $format);
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current timezone
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getTimezone()
|
||||
{
|
||||
return $this->timezoneModel->getCurrentTimezone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session flash message
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function flashMessage()
|
||||
{
|
||||
$success_message = $this->flash->getMessage('success');
|
||||
$failure_message = $this->flash->getMessage('failure');
|
||||
|
||||
if (! empty($success_message)) {
|
||||
return '<div class="alert alert-success alert-fade-out">'.$this->helper->text->e($success_message).'</div>';
|
||||
}
|
||||
|
||||
if (! empty($failure_message)) {
|
||||
return '<div class="alert alert-error">'.$this->helper->text->e($failure_message).'</div>';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Asset Helper
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class AssetHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Add a Javascript asset
|
||||
*
|
||||
* @param string $filename Filename
|
||||
* @param bool $async
|
||||
* @return string
|
||||
*/
|
||||
public function js($filename, $async = false)
|
||||
{
|
||||
$dir = dirname(__DIR__, 2);
|
||||
$filepath = $dir.'/'.$filename;
|
||||
return '<script '.($async ? 'async' : '').' defer type="text/javascript" src="'.$this->helper->url->dir().$filename.'?'.filemtime($filepath).'"></script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a stylesheet asset
|
||||
*
|
||||
* @param string $filename Filename
|
||||
* @param boolean $is_file Add file timestamp
|
||||
* @param string $media Media
|
||||
* @return string
|
||||
*/
|
||||
public function css($filename, $is_file = true, $media = 'screen')
|
||||
{
|
||||
$dir = dirname(__DIR__, 2);
|
||||
$filepath = $dir.'/'.$filename;
|
||||
return '<link rel="stylesheet" href="'.$this->helper->url->dir().$filename.($is_file ? '?'.filemtime($filepath) : '').'" media="'.$media.'">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom css
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function customCss()
|
||||
{
|
||||
if ($this->configModel->get('application_stylesheet')) {
|
||||
return '<style>'.$this->configModel->get('application_stylesheet').'</style>';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSS for task colors
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function colorCss()
|
||||
{
|
||||
return '<style>'.$this->colorModel->getCss().'</style>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Avatar Helper
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class AvatarHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Render user avatar
|
||||
*
|
||||
* @access public
|
||||
* @param string $user_id
|
||||
* @param string $username
|
||||
* @param string $name
|
||||
* @param string $email
|
||||
* @param string $avatar_path
|
||||
* @param string $css
|
||||
* @param int $size
|
||||
* @return string
|
||||
*/
|
||||
public function render($user_id, $username, $name, $email, $avatar_path, $css = 'avatar-left', $size = 48)
|
||||
{
|
||||
if (empty($user_id) && empty($username)) {
|
||||
$html = $this->avatarManager->renderDefault($size);
|
||||
} else {
|
||||
$html = $this->avatarManager->render($user_id, $username, $name, $email, $avatar_path, $size);
|
||||
}
|
||||
|
||||
return '<div class="avatar avatar-'.$size.' '.$css.'">'.$html.'</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render small user avatar
|
||||
*
|
||||
* @access public
|
||||
* @param string $user_id
|
||||
* @param string $username
|
||||
* @param string $name
|
||||
* @param string $email
|
||||
* @param string $avatar_path
|
||||
* @param string $css
|
||||
* @return string
|
||||
*/
|
||||
public function small($user_id, $username, $name, $email, $avatar_path, $css = '')
|
||||
{
|
||||
return $this->render($user_id, $username, $name, $email, $avatar_path, $css, 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a small avatar for the current user
|
||||
*
|
||||
* @access public
|
||||
* @param string $css
|
||||
* @return string
|
||||
*/
|
||||
public function currentUserSmall($css = '')
|
||||
{
|
||||
$user = $this->userSession->getAll();
|
||||
return $this->small($user['id'], $user['username'], $user['name'], $user['email'], $user['avatar_path'], $css);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Model\UserMetadataModel;
|
||||
|
||||
/**
|
||||
* Board Helper
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class BoardHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Return true if tasks are collapsed
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id
|
||||
* @return boolean
|
||||
*/
|
||||
public function isCollapsed($project_id)
|
||||
{
|
||||
return $this->userMetadataCacheDecorator->get(UserMetadataModel::KEY_BOARD_COLLAPSED.$project_id, 0) == 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Model\UserMetadataModel;
|
||||
|
||||
/**
|
||||
* Class CommentHelper
|
||||
*
|
||||
* @package Kanboard\Helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class CommentHelper extends Base
|
||||
{
|
||||
public function toggleSorting()
|
||||
{
|
||||
$oldDirection = $this->userMetadataCacheDecorator->get(UserMetadataModel::KEY_COMMENT_SORTING_DIRECTION, 'ASC');
|
||||
$newDirection = $oldDirection === 'ASC' ? 'DESC' : 'ASC';
|
||||
|
||||
$this->userMetadataCacheDecorator->set(UserMetadataModel::KEY_COMMENT_SORTING_DIRECTION, $newDirection);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use DateTime;
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* DateTime helpers
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class DateHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Get formatted time
|
||||
*
|
||||
* @access public
|
||||
* @param integer $value
|
||||
* @return string
|
||||
*/
|
||||
public function time($value)
|
||||
{
|
||||
return date($this->configModel->get('application_time_format', 'H:i'), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted date
|
||||
*
|
||||
* @access public
|
||||
* @param integer $value
|
||||
* @return string
|
||||
*/
|
||||
public function date($value)
|
||||
{
|
||||
if (empty($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (! ctype_digit((string) $value)) {
|
||||
$value = strtotime($value);
|
||||
}
|
||||
|
||||
return date($this->configModel->get('application_date_format', 'm/d/Y'), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted datetime
|
||||
*
|
||||
* @access public
|
||||
* @param integer $value
|
||||
* @return string
|
||||
*/
|
||||
public function datetime($value)
|
||||
{
|
||||
return date($this->dateParser->getUserDateTimeFormat(), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get duration in seconds into human format
|
||||
*
|
||||
* @access public
|
||||
* @param integer $seconds
|
||||
* @return string
|
||||
*/
|
||||
public function duration($seconds)
|
||||
{
|
||||
if ($seconds == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$dtF = new DateTime("@0");
|
||||
$dtT = new DateTime("@$seconds");
|
||||
|
||||
$format = sprintf("%%a %s, %%h %s, %%i %s, %%s %s", t('days'), t('hours'), t('minutes'), t('seconds'));
|
||||
return $dtF->diff($dtT)->format($format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get duration in hours into human format
|
||||
*
|
||||
* @access public
|
||||
* @param float $hours
|
||||
* @return string
|
||||
*/
|
||||
public function durationHours($hours)
|
||||
{
|
||||
return sprintf('%0.2f %s', round($hours, 2), t('hours'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the age of an item in quasi human readable format.
|
||||
* It's in this format: <1h , NNh, NNd
|
||||
*
|
||||
* @access public
|
||||
* @param integer $timestamp Unix timestamp of the artifact for which age will be calculated
|
||||
* @param integer $now Compare with this timestamp (Default value is the current unix timestamp)
|
||||
* @return string
|
||||
*/
|
||||
public function age($timestamp, $now = null)
|
||||
{
|
||||
if ($now === null) {
|
||||
$now = time();
|
||||
}
|
||||
|
||||
$diff = $now - $timestamp;
|
||||
|
||||
if ($diff < 900) {
|
||||
return t('<15m');
|
||||
}
|
||||
if ($diff < 1200) {
|
||||
return t('<30m');
|
||||
} elseif ($diff < 3600) {
|
||||
return t('<1h');
|
||||
} elseif ($diff < 86400) {
|
||||
return '~'.t('%dh', $diff / 3600);
|
||||
}
|
||||
|
||||
return t('%dd', ($now - $timestamp) / 86400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all hours for day
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getDayHours()
|
||||
{
|
||||
$values = array();
|
||||
|
||||
foreach (range(0, 23) as $hour) {
|
||||
foreach (array(0, 30) as $minute) {
|
||||
$time = sprintf('%02d:%02d', $hour, $minute);
|
||||
$values[$time] = $time;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all days of a week
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getWeekDays()
|
||||
{
|
||||
$values = array();
|
||||
|
||||
foreach (range(1, 7) as $day) {
|
||||
$values[$day] = $this->getWeekDay($day);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the localized day name from the day number
|
||||
*
|
||||
* @access public
|
||||
* @param integer $day Day number
|
||||
* @return string
|
||||
*/
|
||||
public function getWeekDay($day)
|
||||
{
|
||||
return date('l', strtotime('next Monday +'.($day - 1).' days'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* File helpers
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class FileHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Get file icon
|
||||
*
|
||||
* @access public
|
||||
* @param string $filename Filename
|
||||
* @return string Font-Awesome-Icon-Name
|
||||
*/
|
||||
public function icon($filename)
|
||||
{
|
||||
switch (get_file_extension($filename)) {
|
||||
case 'jpeg':
|
||||
case 'jpg':
|
||||
case 'png':
|
||||
case 'gif':
|
||||
case 'svg':
|
||||
return 'fa-file-image-o';
|
||||
case 'xls':
|
||||
case 'xlsx':
|
||||
case 'xlsm':
|
||||
return 'fa-file-excel-o';
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
return 'fa-file-word-o';
|
||||
case 'ppt':
|
||||
case 'pptx':
|
||||
return 'fa-file-powerpoint-o';
|
||||
case 'zip':
|
||||
case 'rar':
|
||||
case 'tar':
|
||||
case 'bz2':
|
||||
case 'xz':
|
||||
case 'gz':
|
||||
return 'fa-file-archive-o';
|
||||
case 'mp3':
|
||||
case 'amr':
|
||||
case 'flac':
|
||||
case 'm4a':
|
||||
case 'ogg':
|
||||
case 'opus':
|
||||
case 'wav':
|
||||
case 'wma':
|
||||
case 'midi':
|
||||
case 'mid':
|
||||
return 'fa-file-audio-o';
|
||||
case 'avi':
|
||||
case 'mov':
|
||||
case 'mp4':
|
||||
case 'mkv':
|
||||
case 'webm':
|
||||
return 'fa-file-video-o';
|
||||
case 'php':
|
||||
case 'html':
|
||||
case 'css':
|
||||
case 'js':
|
||||
return 'fa-file-code-o';
|
||||
case 'pdf':
|
||||
return 'fa-file-pdf-o';
|
||||
}
|
||||
|
||||
return 'fa-file-o';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the image mimetype based on the file extension
|
||||
*
|
||||
* @access public
|
||||
* @param $filename
|
||||
* @return string
|
||||
*/
|
||||
public function getImageMimeType($filename)
|
||||
{
|
||||
switch (get_file_extension($filename)) {
|
||||
case 'jpeg':
|
||||
case 'jpg':
|
||||
return 'image/jpeg';
|
||||
case 'png':
|
||||
return 'image/png';
|
||||
case 'gif':
|
||||
return 'image/gif';
|
||||
default:
|
||||
return 'image/jpeg';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the preview type
|
||||
*
|
||||
* @access public
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
public function getPreviewType($filename)
|
||||
{
|
||||
switch (get_file_extension($filename)) {
|
||||
case 'md':
|
||||
case 'markdown':
|
||||
return 'markdown';
|
||||
case 'txt':
|
||||
return 'text';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the browser view mime-type based on the file extension.
|
||||
*
|
||||
* @access public
|
||||
* @param $filename
|
||||
* @return string
|
||||
*/
|
||||
public function getBrowserViewType($filename)
|
||||
{
|
||||
switch (get_file_extension($filename)) {
|
||||
case 'pdf':
|
||||
return 'application/pdf';
|
||||
case 'mp3':
|
||||
case 'ogg':
|
||||
case 'flac':
|
||||
case 'wav':
|
||||
return 'audio/mpeg';
|
||||
case 'avi':
|
||||
return 'video/x-msvideo';
|
||||
case 'webm':
|
||||
return 'video/webm';
|
||||
case 'mov':
|
||||
return 'video/quicktime';
|
||||
case 'm4v':
|
||||
return 'video/x-m4v';
|
||||
case 'mp4':
|
||||
return 'video/mp4';
|
||||
case 'svg':
|
||||
return 'image/svg+xml';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Form helpers
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class FormHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Hidden CSRF token field
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function csrf()
|
||||
{
|
||||
return '<input type="hidden" name="csrf_token" value="'.$this->token->getCSRFToken().'"/>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a hidden form field
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param array $values Form values
|
||||
* @return string
|
||||
*/
|
||||
public function hidden($name, array $values = array())
|
||||
{
|
||||
return '<input type="hidden" name="'.$name.'" id="form-'.$name.'" '.$this->formValue($values, $name).'/>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a select field
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param array $options Options
|
||||
* @param array $values Form values
|
||||
* @param array $errors Form errors
|
||||
* @param array $attributes
|
||||
* @param string $class CSS class
|
||||
* @return string
|
||||
*/
|
||||
public function select($name, array $options, array $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||
{
|
||||
$html = '<select name="'.$name.'" id="form-'.$name.'" class="'.$class.'" '.implode(' ', $attributes).'>';
|
||||
|
||||
foreach ($options as $id => $value) {
|
||||
$html .= '<option value="'.$this->helper->text->e($id).'"';
|
||||
|
||||
if (isset($values->$name) && $id == $values->$name) {
|
||||
$html .= ' selected="selected"';
|
||||
}
|
||||
if (isset($values[$name]) && $id == $values[$name]) {
|
||||
$html .= ' selected="selected"';
|
||||
}
|
||||
|
||||
$html .= '>'.$this->helper->text->e($value).'</option>';
|
||||
}
|
||||
|
||||
$html .= '</select>';
|
||||
$html .= $this->errorList($errors, $name);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a color select field
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param array $values Form values
|
||||
* @return string
|
||||
*/
|
||||
public function colorSelect($name, array $values)
|
||||
{
|
||||
$colors = $this->colorModel->getList();
|
||||
$html = $this->label(t('Color'), $name);
|
||||
$html .= $this->select($name, $colors, $values, array(), array('tabindex="4"'), 'color-picker');
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a radio field group
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param array $options Options
|
||||
* @param array $values Form values
|
||||
* @return string
|
||||
*/
|
||||
public function radios($name, array $options, array $values = array())
|
||||
{
|
||||
$html = '';
|
||||
|
||||
foreach ($options as $value => $label) {
|
||||
$html .= $this->radio($name, $label, $value, isset($values[$name]) && $values[$name] == $value);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a radio field
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param string $label Form label
|
||||
* @param string $value Form value
|
||||
* @param boolean $selected Field selected or not
|
||||
* @param string $class CSS class
|
||||
* @return string
|
||||
*/
|
||||
public function radio($name, $label, $value, $selected = false, $class = '')
|
||||
{
|
||||
return '<label><input type="radio" name="'.$name.'" class="'.$class.'" value="'.$this->helper->text->e($value).'" '.($selected ? 'checked="checked"' : '').'> '.$this->helper->text->e($label).'</label>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a checkboxes group
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param array $options Options
|
||||
* @param array $values Form values
|
||||
* @return string
|
||||
*/
|
||||
public function checkboxes($name, array $options, array $values = array())
|
||||
{
|
||||
$html = '';
|
||||
|
||||
foreach ($options as $value => $label) {
|
||||
$html .= $this->checkbox($name.'['.$value.']', $label, $value, isset($values[$name]) && in_array($value, $values[$name]));
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a checkbox field
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param string $label Form label
|
||||
* @param string $value Form value
|
||||
* @param boolean $checked Field selected or not
|
||||
* @param string $class CSS class
|
||||
* @param array $attributes
|
||||
* @return string
|
||||
*/
|
||||
public function checkbox($name, $label, $value, $checked = false, $class = '', array $attributes = array())
|
||||
{
|
||||
$htmlAttributes = '';
|
||||
|
||||
if ($checked) {
|
||||
$attributes['checked'] = 'checked';
|
||||
}
|
||||
|
||||
foreach ($attributes as $attribute => $attributeValue) {
|
||||
$htmlAttributes .= sprintf('%s="%s"', $attribute, $this->helper->text->e($attributeValue));
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<label><input type="checkbox" name="%s" class="%s" value="%s" %s> %s</label>',
|
||||
$name,
|
||||
$class,
|
||||
$this->helper->text->e($value),
|
||||
$htmlAttributes,
|
||||
$this->helper->text->e($label)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form label
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param string $label Form label
|
||||
* @param array $attributes HTML attributes
|
||||
* @return string
|
||||
*/
|
||||
public function label($label, $name, array $attributes = array())
|
||||
{
|
||||
return '<label for="form-'.$name.'" '.implode(' ', $attributes).'>'.$this->helper->text->e($label).'</label>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a textarea
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param array $values Form values
|
||||
* @param array $errors Form errors
|
||||
* @param array $attributes HTML attributes
|
||||
* @param string $class CSS class
|
||||
* @return string
|
||||
*/
|
||||
public function textarea($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||
{
|
||||
$class .= $this->errorClass($errors, $name);
|
||||
|
||||
$html = '<textarea name="'.$name.'" id="form-'.$name.'" class="'.$class.'" ';
|
||||
$html .= implode(' ', $attributes).'>';
|
||||
$html .= isset($values[$name]) ? $this->helper->text->e($values[$name]) : '';
|
||||
$html .= '</textarea>';
|
||||
$html .= $this->errorList($errors, $name);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a markdown editor
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param array $values Form values
|
||||
* @param array $errors Form errors
|
||||
* @param array $attributes
|
||||
* @return string
|
||||
*/
|
||||
public function textEditor($name, $values = array(), array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$params = array(
|
||||
'name' => $name,
|
||||
'css' => $this->errorClass($errors, $name),
|
||||
'required' => isset($attributes['required']) && $attributes['required'],
|
||||
'tabindex' => isset($attributes['tabindex']) ? $attributes['tabindex'] : '-1',
|
||||
'labelPreview' => t('Preview'),
|
||||
'previewUrl' => $this->helper->url->to('TaskAjaxController', 'preview'),
|
||||
'labelWrite' => t('Write'),
|
||||
'labelTitle' => t('Title'),
|
||||
'placeholder' => t('Write your text in Markdown'),
|
||||
'ariaLabel' => isset($attributes['aria-label']) ? $attributes['aria-label'] : '',
|
||||
'autofocus' => isset($attributes['autofocus']) && $attributes['autofocus'],
|
||||
'suggestOptions' => array(
|
||||
'triggers' => array(
|
||||
'#' => $this->helper->url->to('TaskAjaxController', 'suggest', array('search' => 'SEARCH_TERM')),
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
if (isset($values['project_id'])) {
|
||||
$params['suggestOptions']['triggers']['@'] = $this->helper->url->to('UserAjaxController', 'mention', array('project_id' => $values['project_id'], 'search' => 'SEARCH_TERM'));
|
||||
}
|
||||
|
||||
$html = '<div class="js-text-editor" data-params=\''.json_encode($params, JSON_HEX_APOS).'\'>';
|
||||
$html .= '<script type="text/template">'.(isset($values[$name]) ? htmlspecialchars($values[$name], ENT_QUOTES, 'UTF-8', true) : '').'</script>';
|
||||
$html .= '</div>';
|
||||
$html .= $this->errorList($errors, $name);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display file field
|
||||
*
|
||||
* @access public
|
||||
* @param string $name
|
||||
* @param array $errors
|
||||
* @param boolean $multiple
|
||||
* @return string
|
||||
*/
|
||||
public function file($name, array $errors = array(), $multiple = false)
|
||||
{
|
||||
$html = '<input type="file" name="'.$name.'" id="form-'.$name.'" '.($multiple ? 'multiple' : '').'>';
|
||||
$html .= $this->errorList($errors, $name);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a input field
|
||||
*
|
||||
* @access public
|
||||
* @param string $type HMTL input tag type
|
||||
* @param string $name Field name
|
||||
* @param array $values Form values
|
||||
* @param array $errors Form errors
|
||||
* @param array $attributes HTML attributes
|
||||
* @param string $class CSS class
|
||||
* @return string
|
||||
*/
|
||||
public function input($type, $name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||
{
|
||||
$class .= $this->errorClass($errors, $name);
|
||||
|
||||
$html = '<input type="'.$type.'" name="'.$name.'" id="form-'.$name.'" '.$this->formValue($values, $name).' class="'.$class.'" ';
|
||||
$html .= implode(' ', $attributes).'>';
|
||||
|
||||
if (in_array('required', $attributes)) {
|
||||
$html .= '<span class="form-required">*</span>';
|
||||
}
|
||||
|
||||
$html .= $this->errorList($errors, $name);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a text field
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param array $values Form values
|
||||
* @param array $errors Form errors
|
||||
* @param array $attributes HTML attributes
|
||||
* @param string $class CSS class
|
||||
* @return string
|
||||
*/
|
||||
public function text($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||
{
|
||||
return $this->input('text', $name, $values, $errors, $attributes, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a password field
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param array $values Form values
|
||||
* @param array $errors Form errors
|
||||
* @param array $attributes HTML attributes
|
||||
* @param string $class CSS class
|
||||
* @return string
|
||||
*/
|
||||
public function password($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||
{
|
||||
return $this->input('password', $name, $values, $errors, $attributes, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an email field
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param array $values Form values
|
||||
* @param array $errors Form errors
|
||||
* @param array $attributes HTML attributes
|
||||
* @param string $class CSS class
|
||||
* @return string
|
||||
*/
|
||||
public function email($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||
{
|
||||
return $this->input('email', $name, $values, $errors, $attributes, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a number field
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param array $values Form values
|
||||
* @param array $errors Form errors
|
||||
* @param array $attributes HTML attributes
|
||||
* @param string $class CSS class
|
||||
* @return string
|
||||
*/
|
||||
public function number($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||
{
|
||||
return $this->input('number', $name, $values, $errors, $attributes, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a numeric field (allow decimal number)
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Field name
|
||||
* @param array $values Form values
|
||||
* @param array $errors Form errors
|
||||
* @param array $attributes HTML attributes
|
||||
* @param string $class CSS class
|
||||
* @return string
|
||||
*/
|
||||
public function numeric($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||
{
|
||||
return $this->input('text', $name, $values, $errors, $attributes, $class.' form-numeric');
|
||||
}
|
||||
|
||||
/**
|
||||
* Date field
|
||||
*
|
||||
* @access public
|
||||
* @param string $label
|
||||
* @param string $name
|
||||
* @param array $values
|
||||
* @param array $errors
|
||||
* @param array $attributes
|
||||
* @return string
|
||||
*/
|
||||
public function date($label, $name, array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$userFormat = $this->dateParser->getUserDateFormat();
|
||||
$values = $this->dateParser->format($values, array($name), $userFormat);
|
||||
$attributes = array_merge(array('placeholder="'.date($userFormat).'"'), $attributes);
|
||||
|
||||
return $this->helper->form->label($label, $name) .
|
||||
$this->helper->form->text($name, $values, $errors, $attributes, 'form-date');
|
||||
}
|
||||
|
||||
/**
|
||||
* Datetime field
|
||||
*
|
||||
* @access public
|
||||
* @param string $label
|
||||
* @param string $name
|
||||
* @param array $values
|
||||
* @param array $errors
|
||||
* @param array $attributes
|
||||
* @return string
|
||||
*/
|
||||
public function datetime($label, $name, array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$userFormat = $this->dateParser->getUserDateTimeFormat();
|
||||
$values = $this->dateParser->format($values, array($name), $userFormat);
|
||||
$attributes = array_merge(array('placeholder="'.date($userFormat).'"'), $attributes);
|
||||
|
||||
return $this->helper->form->label($label, $name) .
|
||||
$this->helper->form->text($name, $values, $errors, $attributes, 'form-datetime');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the form error class
|
||||
*
|
||||
* @access private
|
||||
* @param array $errors Error list
|
||||
* @param string $name Field name
|
||||
* @return string
|
||||
*/
|
||||
private function errorClass(array $errors, $name)
|
||||
{
|
||||
return ! isset($errors[$name]) ? '' : ' form-error';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a list of form errors
|
||||
*
|
||||
* @access private
|
||||
* @param array $errors List of errors
|
||||
* @param string $name Field name
|
||||
* @return string
|
||||
*/
|
||||
private function errorList(array $errors, $name)
|
||||
{
|
||||
$html = '';
|
||||
|
||||
if (isset($errors[$name])) {
|
||||
$html .= '<ul class="form-errors">';
|
||||
|
||||
foreach ($errors[$name] as $error) {
|
||||
$html .= '<li>'.$this->helper->text->e($error).'</li>';
|
||||
}
|
||||
|
||||
$html .= '</ul>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an escaped form value
|
||||
*
|
||||
* @access private
|
||||
* @param mixed $values Values
|
||||
* @param string $name Field name
|
||||
* @return string
|
||||
*/
|
||||
private function formValue($values, $name)
|
||||
{
|
||||
if (isset($values->$name)) {
|
||||
return 'value="'.$this->helper->text->e($values->$name).'"';
|
||||
}
|
||||
|
||||
return isset($values[$name]) ? 'value="'.$this->helper->text->e($values[$name]).'"' : '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Closure;
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Template Hook helpers
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class HookHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Add assets JS or CSS
|
||||
*
|
||||
* @access public
|
||||
* @param string $type
|
||||
* @param string $hook
|
||||
* @return string
|
||||
*/
|
||||
public function asset($type, $hook)
|
||||
{
|
||||
$buffer = '';
|
||||
|
||||
foreach ($this->hook->getListeners($hook) as $params) {
|
||||
$buffer .= $this->helper->asset->$type($params['template']);
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render all attached hooks
|
||||
*
|
||||
* @access public
|
||||
* @param string $hook
|
||||
* @param array $variables
|
||||
* @return string
|
||||
*/
|
||||
public function render($hook, array $variables = array())
|
||||
{
|
||||
$buffer = '';
|
||||
|
||||
foreach ($this->hook->getListeners($hook) as $params) {
|
||||
$currentVariables = $variables;
|
||||
if (! empty($params['variables'])) {
|
||||
$currentVariables = array_merge($variables, $params['variables']);
|
||||
} elseif (! empty($params['callable'])) {
|
||||
$result = call_user_func_array($params['callable'], array_values($variables));
|
||||
|
||||
if (is_array($result)) {
|
||||
$currentVariables = array_merge($variables, $result);
|
||||
}
|
||||
}
|
||||
|
||||
$buffer .= $this->template->render($params['template'], $currentVariables);
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a template to a hook
|
||||
*
|
||||
* @access public
|
||||
* @param string $hook
|
||||
* @param string $template
|
||||
* @param array $variables
|
||||
* @return $this
|
||||
*/
|
||||
public function attach($hook, $template, array $variables = array())
|
||||
{
|
||||
$this->hook->on($hook, array(
|
||||
'template' => $template,
|
||||
'variables' => $variables,
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a template to a hook with a callable
|
||||
*
|
||||
* Arguments passed to the callback are the one passed to the hook
|
||||
*
|
||||
* @access public
|
||||
* @param string $hook
|
||||
* @param string $template
|
||||
* @param Closure $callable
|
||||
* @return $this
|
||||
*/
|
||||
public function attachCallable($hook, $template, Closure $callable)
|
||||
{
|
||||
$this->hook->on($hook, array(
|
||||
'template' => $template,
|
||||
'callable' => $callable,
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Layout Helper
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class LayoutHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Render a template without the layout if Ajax request
|
||||
*
|
||||
* @access public
|
||||
* @param string $template Template name
|
||||
* @param array $params Template parameters
|
||||
* @return string
|
||||
*/
|
||||
public function app($template, array $params = array())
|
||||
{
|
||||
$isAjax = $this->request->isAjax();
|
||||
$params['is_ajax'] = $isAjax;
|
||||
|
||||
if ($isAjax) {
|
||||
return $this->template->render($template, $params);
|
||||
}
|
||||
|
||||
if (! isset($params['no_layout']) && ! isset($params['board_selector'])) {
|
||||
$params['board_selector'] = $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId());
|
||||
|
||||
if (isset($params['project']['id'])) {
|
||||
unset($params['board_selector'][$params['project']['id']]);
|
||||
}
|
||||
|
||||
$this->hook->reference('helper:layout:board-selector:list', $params['board_selector']);
|
||||
}
|
||||
|
||||
return $this->pageLayout($template, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common layout for user views
|
||||
*
|
||||
* @access public
|
||||
* @param string $template Template name
|
||||
* @param array $params Template parameters
|
||||
* @return string
|
||||
*/
|
||||
public function user($template, array $params)
|
||||
{
|
||||
if (isset($params['user'])) {
|
||||
$params['title'] = '#'.$params['user']['id'].' '.($params['user']['name'] ?: $params['user']['username']);
|
||||
}
|
||||
|
||||
return $this->subLayout('user_view/layout', 'user_view/sidebar', $template, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common layout for task views
|
||||
*
|
||||
* @access public
|
||||
* @param string $template Template name
|
||||
* @param array $params Template parameters
|
||||
* @return string
|
||||
*/
|
||||
public function task($template, array $params)
|
||||
{
|
||||
$params['page_title'] = $params['task']['project_name'].', #'.$params['task']['id'].' - '.$params['task']['title'];
|
||||
$params['title'] = $params['task']['project_name'];
|
||||
return $this->subLayout('task/layout', 'task/sidebar', $template, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common layout for project views
|
||||
*
|
||||
* @access public
|
||||
* @param string $template
|
||||
* @param array $params
|
||||
* @param string $sidebar
|
||||
* @return string
|
||||
*/
|
||||
public function project($template, array $params, $sidebar = 'project/sidebar')
|
||||
{
|
||||
if (empty($params['title'])) {
|
||||
$params['title'] = $params['project']['name'];
|
||||
} elseif ($params['project']['name'] !== $params['title']) {
|
||||
$params['title'] = $params['project']['name'].' > '.$params['title'];
|
||||
}
|
||||
|
||||
return $this->subLayout('project/layout', $sidebar, $template, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common layout for project user views
|
||||
*
|
||||
* @access public
|
||||
* @param string $template
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
public function projectUser($template, array $params)
|
||||
{
|
||||
$params['filter'] = array('user_id' => $params['user_id']);
|
||||
return $this->subLayout('project_user_overview/layout', 'project_user_overview/sidebar', $template, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common layout for config views
|
||||
*
|
||||
* @access public
|
||||
* @param string $template
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
public function config($template, array $params)
|
||||
{
|
||||
if (empty($params['values'])) {
|
||||
$params['values'] = $this->configModel->getAll();
|
||||
}
|
||||
|
||||
if (! isset($params['errors'])) {
|
||||
$params['errors'] = array();
|
||||
}
|
||||
|
||||
return $this->subLayout('config/layout', 'config/sidebar', $template, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common layout for plugin views
|
||||
*
|
||||
* @access public
|
||||
* @param string $template
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
public function plugin($template, array $params)
|
||||
{
|
||||
return $this->subLayout('plugin/layout', 'plugin/sidebar', $template, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common layout for dashboard views
|
||||
*
|
||||
* @access public
|
||||
* @param string $template
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
public function dashboard($template, array $params)
|
||||
{
|
||||
return $this->subLayout('dashboard/layout', 'dashboard/sidebar', $template, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common layout for analytic views
|
||||
*
|
||||
* @access public
|
||||
* @param string $template
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
public function analytic($template, array $params)
|
||||
{
|
||||
if (isset($params['project']['name'])) {
|
||||
$params['title'] = $params['project']['name'].' > '.$params['title'];
|
||||
}
|
||||
|
||||
return $this->subLayout('analytic/layout', 'analytic/sidebar', $template, $params, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render page layout
|
||||
*
|
||||
* @access public
|
||||
* @param string $template Template name
|
||||
* @param array $params Key/value dictionary
|
||||
* @param string $layout Layout name
|
||||
* @return string
|
||||
*/
|
||||
public function pageLayout($template, array $params = array(), $layout = 'layout')
|
||||
{
|
||||
return $this->template->render(
|
||||
$layout,
|
||||
$params + array('content_for_layout' => $this->template->render($template, $params))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common method to generate a sub-layout
|
||||
*
|
||||
* @access public
|
||||
* @param string $sublayout
|
||||
* @param string $sidebar
|
||||
* @param string $template
|
||||
* @param array $params
|
||||
* @param bool $ignoreAjax
|
||||
* @return string
|
||||
*/
|
||||
public function subLayout($sublayout, $sidebar, $template, array $params = array(), $ignoreAjax = false)
|
||||
{
|
||||
$isAjax = $this->request->isAjax();
|
||||
$params['is_ajax'] = $isAjax;
|
||||
$content = $this->template->render($template, $params);
|
||||
|
||||
if (!$ignoreAjax && $isAjax) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$params['content_for_sublayout'] = $content;
|
||||
$params['sidebar_template'] = $sidebar;
|
||||
|
||||
return $this->app($sublayout, $params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Class MailHelper
|
||||
*
|
||||
* @package Kanboard\Helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class MailHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Filter mail subject
|
||||
*
|
||||
* @access public
|
||||
* @param string $subject
|
||||
* @return string
|
||||
*/
|
||||
public function filterSubject($subject)
|
||||
{
|
||||
$subject = str_ireplace('RE: ', '', $subject);
|
||||
$subject = str_ireplace('FW: ', '', $subject);
|
||||
$subject = str_ireplace('Fwd: ', '', $subject);
|
||||
|
||||
return $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mail sender address
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getMailSenderAddress()
|
||||
{
|
||||
if (MAIL_CONFIGURATION) {
|
||||
$email = $this->configModel->get('mail_sender_address');
|
||||
|
||||
if (! empty($email)) {
|
||||
return $email;
|
||||
}
|
||||
}
|
||||
|
||||
return MAIL_FROM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mail transport
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getMailTransport()
|
||||
{
|
||||
if (MAIL_CONFIGURATION) {
|
||||
$transport = $this->configModel->get('mail_transport');
|
||||
|
||||
if (! empty($transport)) {
|
||||
return $transport;
|
||||
}
|
||||
}
|
||||
|
||||
return MAIL_TRANSPORT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Class ModalHelper
|
||||
*
|
||||
* @package Kanboard\Helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ModalHelper extends Base
|
||||
{
|
||||
public function submitButtons(array $params = array())
|
||||
{
|
||||
return $this->helper->app->component('submit-buttons', array(
|
||||
'submitLabel' => isset($params['submitLabel']) ? $params['submitLabel'] : t('Save'),
|
||||
'orLabel' => t('or'),
|
||||
'cancelLabel' => t('cancel'),
|
||||
'color' => isset($params['color']) ? $params['color'] : 'blue',
|
||||
'tabindex' => isset($params['tabindex']) ? $params['tabindex'] : null,
|
||||
'disabled' => isset($params['disabled']) ? true : false,
|
||||
));
|
||||
}
|
||||
|
||||
public function confirmButtons($controller, $action, array $params = array(), $submitLabel = '', $tabindex = null)
|
||||
{
|
||||
return $this->helper->app->component('confirm-buttons', array(
|
||||
'url' => $this->helper->url->href($controller, $action, $params, true),
|
||||
'submitLabel' => $submitLabel ?: t('Yes'),
|
||||
'orLabel' => t('or'),
|
||||
'cancelLabel' => t('cancel'),
|
||||
'tabindex' => $tabindex,
|
||||
));
|
||||
}
|
||||
|
||||
public function largeIcon($icon, $label, $controller, $action, array $params = array())
|
||||
{
|
||||
$html = '<i class="fa fa-'.$icon.' fa-fw js-modal-large" aria-hidden="true"></i>';
|
||||
return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-large', $label);
|
||||
}
|
||||
|
||||
public function large($icon, $label, $controller, $action, array $params = array())
|
||||
{
|
||||
$html = '<i class="fa fa-'.$icon.' fa-fw js-modal-large" aria-hidden="true"></i>'.$label;
|
||||
return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-large');
|
||||
}
|
||||
|
||||
public function medium($icon, $label, $controller, $action, array $params = array(), $title = '')
|
||||
{
|
||||
$ariaLabel = (empty($title) ? 'aria-hidden="true"' : 'role="img" aria-label="'.$title.'"');
|
||||
$html = '<i class="fa fa-'.$icon.' fa-fw js-modal-medium" '.$ariaLabel.'></i>'.$label;
|
||||
return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-medium', $title);
|
||||
}
|
||||
|
||||
public function small($icon, $label, $controller, $action, array $params = array())
|
||||
{
|
||||
$html = '<i class="fa fa-'.$icon.' fa-fw js-modal-small" aria-hidden="true"></i>'.$label;
|
||||
return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-small');
|
||||
}
|
||||
|
||||
public function mediumButton($icon, $label, $controller, $action, array $params = array())
|
||||
{
|
||||
$html = '<i class="fa fa-'.$icon.' fa-fw js-modal-medium" aria-hidden="true"></i>'.$label;
|
||||
return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-medium btn');
|
||||
}
|
||||
|
||||
public function mediumIcon($icon, $label, $controller, $action, array $params = array())
|
||||
{
|
||||
$html = '<i class="fa fa-'.$icon.' fa-fw js-modal-medium" aria-hidden="true"></i>';
|
||||
return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-medium', $label);
|
||||
}
|
||||
|
||||
public function confirm($icon, $label, $controller, $action, array $params = array())
|
||||
{
|
||||
$html = '<i class="fa fa-'.$icon.' fa-fw js-modal-confirm" aria-hidden="true"></i>'.$label;
|
||||
return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-confirm');
|
||||
}
|
||||
|
||||
public function confirmLink($label, $controller, $action, array $params = array())
|
||||
{
|
||||
return $this->helper->url->link($label, $controller, $action, $params, false, 'js-modal-confirm');
|
||||
}
|
||||
|
||||
public function replaceLink($label, $controller, $action, array $params = array())
|
||||
{
|
||||
return $this->helper->url->link($label, $controller, $action, $params, false, 'js-modal-replace');
|
||||
}
|
||||
|
||||
public function replaceIconLink($icon, $label, $controller, $action, array $params = array())
|
||||
{
|
||||
$html = '<i class="fa fa-'.$icon.' fa-fw js-modal-replace" aria-hidden="true"></i>'.$label;
|
||||
return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-replace');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Model Helper
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ModelHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Remove keys from an array
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Input array
|
||||
* @param string[] $keys List of keys to remove
|
||||
*/
|
||||
public function removeFields(array &$values, array $keys)
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
if (array_key_exists($key, $values)) {
|
||||
unset($values[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove keys from an array if empty
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Input array
|
||||
* @param string[] $keys List of keys to remove
|
||||
*/
|
||||
public function removeEmptyFields(array &$values, array $keys)
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
if (array_key_exists($key, $values) && empty($values[$key])) {
|
||||
unset($values[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force fields to be at 0 if empty
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Input array
|
||||
* @param string[] $keys List of keys
|
||||
*/
|
||||
public function resetFields(array &$values, array $keys)
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
if (isset($values[$key]) && empty($values[$key])) {
|
||||
$values[$key] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force some fields to be integer
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Input array
|
||||
* @param string[] $keys List of keys
|
||||
*/
|
||||
public function convertIntegerFields(array &$values, array $keys)
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
if (isset($values[$key])) {
|
||||
$values[$key] = (int) $values[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force some fields to be null if empty
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Input array
|
||||
* @param string[] $keys List of keys
|
||||
*/
|
||||
public function convertNullFields(array &$values, array $keys)
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
if (array_key_exists($key, $values) && empty($values[$key])) {
|
||||
$values[$key] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Filter\ProjectActivityProjectIdFilter;
|
||||
use Kanboard\Filter\ProjectActivityProjectIdsFilter;
|
||||
use Kanboard\Filter\ProjectActivityTaskIdFilter;
|
||||
use Kanboard\Model\ProjectActivityModel;
|
||||
|
||||
/**
|
||||
* Project Activity Helper
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectActivityHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Search events
|
||||
*
|
||||
* @access public
|
||||
* @param string $search
|
||||
* @return array
|
||||
*/
|
||||
public function searchEvents($search)
|
||||
{
|
||||
$projects = $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId());
|
||||
$events = array();
|
||||
|
||||
if ($search !== '') {
|
||||
$queryBuilder = $this->projectActivityLexer->build($search);
|
||||
$queryBuilder
|
||||
->withFilter(new ProjectActivityProjectIdsFilter(array_keys($projects)))
|
||||
->getQuery()
|
||||
->desc(ProjectActivityModel::TABLE.'.id')
|
||||
->limit(500)
|
||||
;
|
||||
|
||||
$events = $queryBuilder->format($this->projectActivityEventFormatter);
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project activity events
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function getProjectEvents($project_id, $limit = 50)
|
||||
{
|
||||
$queryBuilder = $this->projectActivityQuery
|
||||
->withFilter(new ProjectActivityProjectIdFilter($project_id));
|
||||
|
||||
$queryBuilder->getQuery()
|
||||
->desc(ProjectActivityModel::TABLE.'.id')
|
||||
->limit($limit)
|
||||
;
|
||||
|
||||
return $queryBuilder->format($this->projectActivityEventFormatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get projects activity events
|
||||
*
|
||||
* @access public
|
||||
* @param int[] $project_ids
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function getProjectsEvents(array $project_ids, $limit = 50)
|
||||
{
|
||||
$queryBuilder = $this->projectActivityQuery
|
||||
->withFilter(new ProjectActivityProjectIdsFilter($project_ids));
|
||||
|
||||
$queryBuilder->getQuery()
|
||||
->desc(ProjectActivityModel::TABLE.'.id')
|
||||
->limit($limit)
|
||||
;
|
||||
|
||||
return $queryBuilder->format($this->projectActivityEventFormatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task activity events
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id
|
||||
* @return array
|
||||
*/
|
||||
public function getTaskEvents($task_id)
|
||||
{
|
||||
$queryBuilder = $this->projectActivityQuery
|
||||
->withFilter(new ProjectActivityTaskIdFilter($task_id));
|
||||
|
||||
$queryBuilder->getQuery()->desc(ProjectActivityModel::TABLE.'.id');
|
||||
|
||||
return $queryBuilder->format($this->projectActivityEventFormatter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Project Header Helper
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectHeaderHelper extends Base
|
||||
{
|
||||
public $hookBefore = '';
|
||||
public $dropdownHtml = '';
|
||||
public $viewsHtml = '';
|
||||
public $searchHtml = '';
|
||||
public $hookAfter = '';
|
||||
/**
|
||||
* Get current search query
|
||||
*
|
||||
* @access public
|
||||
* @param array $project
|
||||
* @return string
|
||||
*/
|
||||
public function getSearchQuery(array $project)
|
||||
{
|
||||
$search = $this->request->getStringParam('search', $this->userSession->getFilters($project['id']));
|
||||
$this->userSession->setFilters($project['id'], $search);
|
||||
return rawurldecode($search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render project header (views switcher and search box)
|
||||
*
|
||||
* @access public
|
||||
* @param array $project
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @param bool $boardView
|
||||
* @param string $plugin
|
||||
* @return string
|
||||
*/
|
||||
public function render(array $project, $controller, $action, $boardView = false, $plugin = '')
|
||||
{
|
||||
$filters = array(
|
||||
'controller' => $controller,
|
||||
'action' => $action,
|
||||
'project_id' => $project['id'],
|
||||
'search' => $this->getSearchQuery($project),
|
||||
'plugin' => $plugin,
|
||||
);
|
||||
|
||||
return $this->template->render('project_header/header', array(
|
||||
'project' => $project,
|
||||
'filters' => $filters,
|
||||
'categories_list' => $this->categoryModel->getList($project['id'], false),
|
||||
'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id'], false),
|
||||
'custom_filters_list' => $this->customFilterModel->getAll($project['id'], $this->userSession->getId()),
|
||||
'board_view' => $boardView,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project description
|
||||
*
|
||||
* @access public
|
||||
* @param array &$project
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(array &$project)
|
||||
{
|
||||
if ($project['owner_id'] > 0) {
|
||||
$description = t('Project owner: ').'<strong>'.$this->helper->text->e($project['owner_name'] ?: $project['owner_username']).'</strong>'.PHP_EOL.PHP_EOL;
|
||||
|
||||
if (! empty($project['description'])) {
|
||||
$description .= '<hr>'.PHP_EOL.PHP_EOL;
|
||||
$description .= $this->helper->text->markdown($project['description'] ?: '');
|
||||
}
|
||||
} else {
|
||||
$description = $this->helper->text->markdown($project['description'] ?: '');
|
||||
}
|
||||
|
||||
return $description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Core\Security\Role;
|
||||
use Kanboard\Model\ColumnRestrictionModel;
|
||||
use Kanboard\Model\ProjectRoleRestrictionModel;
|
||||
|
||||
/**
|
||||
* Class ProjectRoleHelper
|
||||
*
|
||||
* @package Kanboard\Helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectRoleHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Get project role for the current user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $projectId
|
||||
* @return string
|
||||
*/
|
||||
public function getProjectUserRole($projectId)
|
||||
{
|
||||
return $this->memoryCache->proxy($this->projectUserRoleModel, 'getUserRole', $projectId, $this->userSession->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the task can be moved by the logged user
|
||||
*
|
||||
* @param array $task
|
||||
* @return bool
|
||||
*/
|
||||
public function isDraggable(array &$task)
|
||||
{
|
||||
if ($task['is_active'] == 1 && $this->helper->user->hasProjectAccess('BoardAjaxController', 'save', $task['project_id'])) {
|
||||
return $this->isSortableColumn($task['project_id'], $task['column_id'], $task['owner_id']);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true is the column is sortable
|
||||
*
|
||||
* @param int $projectId
|
||||
* @param int $columnId
|
||||
* @param int $assigneeId
|
||||
* @return bool
|
||||
*/
|
||||
public function isSortableColumn($projectId, $columnId, $assigneeId = null)
|
||||
{
|
||||
$role = $this->getProjectUserRole($projectId);
|
||||
|
||||
if ($this->role->isCustomProjectRole($role)) {
|
||||
$sortableColumns = $this->columnMoveRestrictionCacheDecorator->getSortableColumns($projectId, $role);
|
||||
|
||||
foreach ($sortableColumns as $column) {
|
||||
if ($column['src_column_id'] == $columnId || $column['dst_column_id'] == $columnId) {
|
||||
if ($column['only_assigned'] == 1 && $assigneeId !== null && $assigneeId != $this->userSession->getId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return empty($sortableColumns) && $this->isAllowedToMoveTask($projectId, $role);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user can move a task
|
||||
*
|
||||
* @param int $projectId
|
||||
* @param int $srcColumnId
|
||||
* @param int $dstColumnId
|
||||
* @return bool|int
|
||||
*/
|
||||
public function canMoveTask($projectId, $srcColumnId, $dstColumnId)
|
||||
{
|
||||
$role = $this->getProjectUserRole($projectId);
|
||||
|
||||
if ($this->role->isCustomProjectRole($role)) {
|
||||
if ($srcColumnId == $dstColumnId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$sortableColumns = $this->columnMoveRestrictionCacheDecorator->getSortableColumns($projectId, $role);
|
||||
|
||||
foreach ($sortableColumns as $column) {
|
||||
if ($column['src_column_id'] == $srcColumnId && $column['dst_column_id'] == $dstColumnId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($column['dst_column_id'] == $srcColumnId && $column['src_column_id'] == $dstColumnId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return empty($sortableColumns) && $this->isAllowedToMoveTask($projectId, $role);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the user can create a task for the given column
|
||||
*
|
||||
* @param int $projectId
|
||||
* @param int $columnId
|
||||
* @return bool
|
||||
*/
|
||||
public function canCreateTaskInColumn($projectId, $columnId)
|
||||
{
|
||||
$role = $this->getProjectUserRole($projectId);
|
||||
|
||||
if ($this->role->isCustomProjectRole($role)) {
|
||||
if (! $this->isAllowedToCreateTask($projectId, $columnId, $role)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->helper->user->hasProjectAccess('TaskCreationController', 'show', $projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the user can create a task for the given column
|
||||
*
|
||||
* @param int $projectId
|
||||
* @param int $columnId
|
||||
* @return bool
|
||||
*/
|
||||
public function canChangeTaskStatusInColumn($projectId, $columnId)
|
||||
{
|
||||
$role = $this->getProjectUserRole($projectId);
|
||||
|
||||
if ($this->role->isCustomProjectRole($role)) {
|
||||
if (! $this->isAllowedToChangeTaskStatus($projectId, $columnId, $role)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->helper->user->hasProjectAccess('TaskStatusController', 'close', $projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the user can remove a task
|
||||
*
|
||||
* Regular users can't remove tasks from other people
|
||||
*
|
||||
* @public
|
||||
* @param array $task
|
||||
* @return bool
|
||||
*/
|
||||
public function canRemoveTask(array $task)
|
||||
{
|
||||
$role = $this->getProjectUserRole($task['project_id']);
|
||||
|
||||
if ($this->hasRestriction($task['project_id'], $role, ProjectRoleRestrictionModel::RULE_TASK_SUPPRESSION)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($task['creator_id']) && $task['creator_id'] == $this->userSession->getId()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->userSession->isAdmin() || $this->getProjectUserRole($task['project_id']) === Role::PROJECT_MANAGER) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the user can change assignee
|
||||
*
|
||||
* @public
|
||||
* @param array $task
|
||||
* @return bool
|
||||
*/
|
||||
public function canChangeAssignee(array $task)
|
||||
{
|
||||
$role = $this->getProjectUserRole($task['project_id']);
|
||||
|
||||
if ($this->role->isCustomProjectRole($role) && $this->hasRestriction($task['project_id'], $role, ProjectRoleRestrictionModel::RULE_TASK_CHANGE_ASSIGNEE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the user can update a task
|
||||
*
|
||||
* @public
|
||||
* @param array $task
|
||||
* @return bool
|
||||
*/
|
||||
public function canUpdateTask(array $task)
|
||||
{
|
||||
$role = $this->getProjectUserRole($task['project_id']);
|
||||
|
||||
if ($this->role->isCustomProjectRole($role) && $task['owner_id'] != $this->userSession->getId() && $this->hasRestriction($task['project_id'], $role, ProjectRoleRestrictionModel::RULE_TASK_UPDATE_ASSIGNED)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check project access
|
||||
*
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @param integer $projectId
|
||||
* @return bool
|
||||
*/
|
||||
public function checkProjectAccess($controller, $action, $projectId)
|
||||
{
|
||||
if (! $this->userSession->isLogged()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->userSession->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $this->helper->user->hasAccess($controller, $action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$role = $this->getProjectUserRole($projectId);
|
||||
|
||||
if ($this->role->isCustomProjectRole($role)) {
|
||||
$result = $this->projectAuthorization->isAllowed($controller, $action, Role::PROJECT_MEMBER);
|
||||
} else {
|
||||
$result = $this->projectAuthorization->isAllowed($controller, $action, $role);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check authorization for a custom project role to change the task status
|
||||
*
|
||||
* @param int $projectId
|
||||
* @param int $columnId
|
||||
* @param string $role
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAllowedToChangeTaskStatus($projectId, $columnId, $role)
|
||||
{
|
||||
$columnRestrictions = $this->columnRestrictionCacheDecorator->getAllByRole($projectId, $role);
|
||||
|
||||
foreach ($columnRestrictions as $restriction) {
|
||||
if ($restriction['column_id'] == $columnId) {
|
||||
if ($restriction['rule'] == ColumnRestrictionModel::RULE_ALLOW_TASK_OPEN_CLOSE) {
|
||||
return true;
|
||||
} elseif ($restriction['rule'] == ColumnRestrictionModel::RULE_BLOCK_TASK_OPEN_CLOSE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ! $this->hasRestriction($projectId, $role, ProjectRoleRestrictionModel::RULE_TASK_OPEN_CLOSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check authorization for a custom project role to create a task
|
||||
*
|
||||
* @param int $projectId
|
||||
* @param int $columnId
|
||||
* @param string $role
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAllowedToCreateTask($projectId, $columnId, $role)
|
||||
{
|
||||
$columnRestrictions = $this->columnRestrictionCacheDecorator->getAllByRole($projectId, $role);
|
||||
|
||||
foreach ($columnRestrictions as $restriction) {
|
||||
if ($restriction['column_id'] == $columnId) {
|
||||
if ($restriction['rule'] == ColumnRestrictionModel::RULE_ALLOW_TASK_CREATION) {
|
||||
return true;
|
||||
} elseif ($restriction['rule'] == ColumnRestrictionModel::RULE_BLOCK_TASK_CREATION) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ! $this->hasRestriction($projectId, $role, ProjectRoleRestrictionModel::RULE_TASK_CREATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the role can move task in the given project
|
||||
*
|
||||
* @param int $projectId
|
||||
* @param string $role
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAllowedToMoveTask($projectId, $role)
|
||||
{
|
||||
$projectRestrictions = $this->projectRoleRestrictionCacheDecorator->getAllByRole($projectId, $role);
|
||||
|
||||
foreach ($projectRestrictions as $restriction) {
|
||||
if ($restriction['rule'] == ProjectRoleRestrictionModel::RULE_TASK_MOVE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given role has a restriction
|
||||
*
|
||||
* @param integer $projectId
|
||||
* @param string $role
|
||||
* @param string $rule
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasRestriction($projectId, $role, $rule)
|
||||
{
|
||||
$projectRestrictions = $this->projectRoleRestrictionCacheDecorator->getAllByRole($projectId, $role);
|
||||
|
||||
foreach ($projectRestrictions as $restriction) {
|
||||
if ($restriction['rule'] == $rule) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
use Kanboard\Model\SubtaskModel;
|
||||
|
||||
/**
|
||||
* Subtask helpers
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class SubtaskHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Return if the current user has a subtask in progress
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSubtaskInProgress()
|
||||
{
|
||||
return session_is_true('hasSubtaskInProgress');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render subtask title
|
||||
*
|
||||
* @param array $subtask
|
||||
* @return string
|
||||
*/
|
||||
public function renderTitle(array $subtask)
|
||||
{
|
||||
if ($subtask['status'] == 0) {
|
||||
$html = '<i class="fa fa-square-o fa-fw ' . ($this->hasSubtaskInProgress() ? 'js-modal-confirm' : '') . '"></i>';
|
||||
} elseif ($subtask['status'] == 1) {
|
||||
$html = '<i class="fa fa-gears fa-fw"></i>';
|
||||
} else {
|
||||
$html = '<i class="fa fa-check-square-o fa-fw"></i>';
|
||||
}
|
||||
|
||||
return $html.$this->helper->text->e($subtask['title']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link to toggle subtask status
|
||||
*
|
||||
* @access public
|
||||
* @param array $task
|
||||
* @param array $subtask
|
||||
* @param string $fragment
|
||||
* @param int $userId
|
||||
* @return string
|
||||
*/
|
||||
public function renderToggleStatus(array $task, array $subtask, $fragment = '', $userId = 0)
|
||||
{
|
||||
if (! $this->helper->user->hasProjectAccess('SubtaskController', 'edit', $task['project_id'])) {
|
||||
$html = $this->renderTitle($subtask);
|
||||
} else {
|
||||
$title = $this->renderTitle($subtask);
|
||||
$params = array(
|
||||
'project_id' => $task['project_id'],
|
||||
'task_id' => $subtask['task_id'],
|
||||
'subtask_id' => $subtask['id'],
|
||||
'user_id' => $userId,
|
||||
'fragment' => $fragment,
|
||||
'csrf_token' => $this->token->getReusableCSRFToken(),
|
||||
);
|
||||
|
||||
if ($subtask['status'] == 0 && $this->hasSubtaskInProgress()) {
|
||||
$html = $this->helper->url->link($title, 'SubtaskRestrictionController', 'show', $params, false, 'js-modal-confirm', $this->getSubtaskTooltip($subtask));
|
||||
} else {
|
||||
$html = $this->helper->url->link($title, 'SubtaskStatusController', 'change', $params, false, 'js-subtask-toggle-status', $this->getSubtaskTooltip($subtask));
|
||||
}
|
||||
}
|
||||
|
||||
return '<span class="subtask-title">'.$html.'</span>';
|
||||
}
|
||||
|
||||
public function renderTimer(array $task, array $subtask)
|
||||
{
|
||||
$html = '<span class="subtask-timer-toggle">';
|
||||
$params = array(
|
||||
'task_id' => $subtask['task_id'],
|
||||
'subtask_id' => $subtask['id'],
|
||||
'timer' => '',
|
||||
'csrf_token' => $this->token->getReusableCSRFToken(),
|
||||
);
|
||||
|
||||
if ($subtask['is_timer_started']) {
|
||||
$params['timer'] = 'stop';
|
||||
$html .= $this->helper->url->icon('pause', t('Stop timer'), 'SubtaskStatusController', 'timer', $params, false, 'js-subtask-toggle-timer');
|
||||
$html .= ' (' . $this->helper->dt->age($subtask['timer_start_date']) .')';
|
||||
} else {
|
||||
$params['timer'] = 'start';
|
||||
$html .= $this->helper->url->icon('play-circle-o', t('Start timer'), 'SubtaskStatusController', 'timer', $params, false, 'js-subtask-toggle-timer');
|
||||
}
|
||||
|
||||
$html .= '</span>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderBulkTitleField(array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="1"', 'required'), $attributes);
|
||||
|
||||
$html = $this->helper->form->label(t('Title'), 'title');
|
||||
$html .= $this->helper->form->textarea('title', $values, $errors, $attributes);
|
||||
$html .= '<p class="form-help">'.t('Enter one subtask by line.').'</p>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderTitleField(array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="1"', 'required'), $attributes);
|
||||
|
||||
$html = $this->helper->form->label(t('Title'), 'title');
|
||||
$html .= $this->helper->form->text('title', $values, $errors, $attributes, 'form-max-width');
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderAssigneeField(array $users, array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="2"'), $attributes);
|
||||
|
||||
$html = $this->helper->form->label(t('Assignee'), 'user_id');
|
||||
$html .= $this->helper->form->select('user_id', $users, $values, $errors, $attributes);
|
||||
$html .= ' ';
|
||||
$html .= '<small>';
|
||||
$html .= '<a href="#" class="assign-me" data-target-id="form-user_id" data-current-id="'.$this->userSession->getId().'" title="'.t('Assign to me').'" aria-label="'.t('Assign to me').'">'.t('Me').'</a>';
|
||||
$html .= '</small>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderTimeEstimatedField(array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="3"'), $attributes);
|
||||
|
||||
$html = $this->helper->form->label(t('Original estimate'), 'time_estimated');
|
||||
$html .= $this->helper->form->numeric('time_estimated', $values, $errors, $attributes);
|
||||
$html .= ' '.t('hours');
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderTimeSpentField(array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="4"'), $attributes);
|
||||
|
||||
$html = $this->helper->form->label(t('Time spent'), 'time_spent');
|
||||
$html .= $this->helper->form->numeric('time_spent', $values, $errors, $attributes);
|
||||
$html .= ' '.t('hours');
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function getSubtaskTooltip(array $subtask)
|
||||
{
|
||||
switch ($subtask['status']) {
|
||||
case SubtaskModel::STATUS_TODO:
|
||||
return t('Subtask not started');
|
||||
case SubtaskModel::STATUS_INPROGRESS:
|
||||
return t('Subtask currently in progress');
|
||||
case SubtaskModel::STATUS_DONE:
|
||||
return t('Subtask completed');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Task helpers
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskHelper extends Base
|
||||
{
|
||||
/**
|
||||
* Local cache for project columns
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $columns = array();
|
||||
|
||||
public function getColors()
|
||||
{
|
||||
return $this->colorModel->getList();
|
||||
}
|
||||
|
||||
public function recurrenceTriggers()
|
||||
{
|
||||
return $this->taskRecurrenceModel->getRecurrenceTriggerList();
|
||||
}
|
||||
|
||||
public function recurrenceTimeframes()
|
||||
{
|
||||
return $this->taskRecurrenceModel->getRecurrenceTimeframeList();
|
||||
}
|
||||
|
||||
public function recurrenceBasedates()
|
||||
{
|
||||
return $this->taskRecurrenceModel->getRecurrenceBasedateList();
|
||||
}
|
||||
|
||||
public function renderTitleField(array $values, array $errors)
|
||||
{
|
||||
$html = $this->helper->form->label(t('Title'), 'title', ['class="ui-helper-hidden-accessible"']);
|
||||
$html .= $this->helper->form->text(
|
||||
'title',
|
||||
$values,
|
||||
$errors,
|
||||
array(
|
||||
'autofocus',
|
||||
'required',
|
||||
'tabindex="1"',
|
||||
'placeholder="'.t('Title').'"'
|
||||
)
|
||||
);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderDescriptionField(array $values, array $errors)
|
||||
{
|
||||
return $this->helper->form->textEditor('description', $values, $errors, array('tabindex' => 2, 'aria-label' => t('Description')));
|
||||
}
|
||||
|
||||
public function renderDescriptionTemplateDropdown($projectId)
|
||||
{
|
||||
$templates = $this->predefinedTaskDescriptionModel->getAll($projectId);
|
||||
|
||||
if (! empty($templates)) {
|
||||
$html = '<div class="dropdown dropdown-smaller">';
|
||||
$html .= '<a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-floppy-o fa-fw" aria-hidden="true"></i>'.t('Template for the task description').' <i class="fa fa-caret-down" aria-hidden="true"></i></a>';
|
||||
$html .= '<ul>';
|
||||
|
||||
foreach ($templates as $template) {
|
||||
$html .= '<li>';
|
||||
$html .= '<a href="#" data-template-target="textarea[name=description]" data-template="'.$this->helper->text->e($template['description']).'" class="js-template">';
|
||||
$html .= $this->helper->text->e($template['title']);
|
||||
$html .= '</a>';
|
||||
$html .= '</li>';
|
||||
}
|
||||
|
||||
$html .= '</ul></div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function renderTagField(array $project, array $tags = array())
|
||||
{
|
||||
$options = $this->tagModel->getAssignableList($project['id'], $project['enable_global_tags']);
|
||||
|
||||
$html = $this->helper->form->label(t('Tags'), 'tags[]');
|
||||
$html .= '<input type="hidden" name="tags[]" value="">';
|
||||
$html .= '<select name="tags[]" id="form-tags" class="tag-autocomplete" multiple tabindex="3">';
|
||||
|
||||
foreach ($options as $tag) {
|
||||
$html .= sprintf(
|
||||
'<option value="%s" %s>%s</option>',
|
||||
$this->helper->text->e($tag),
|
||||
in_array($tag, $tags) ? 'selected="selected"' : '',
|
||||
$this->helper->text->e($tag)
|
||||
);
|
||||
}
|
||||
|
||||
$html .= '</select>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderColorField(array $values)
|
||||
{
|
||||
$html = $this->helper->form->colorSelect('color_id', $values);
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderAssigneeField(array $users, array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
if (isset($values['project_id']) && ! $this->helper->projectRole->canChangeAssignee($values)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$attributes = array_merge(array('tabindex="5"'), $attributes);
|
||||
|
||||
$html = $this->helper->form->label(t('Assignee'), 'owner_id');
|
||||
$html .= $this->helper->form->select('owner_id', $users, $values, $errors, $attributes);
|
||||
$html .= ' ';
|
||||
$html .= '<small>';
|
||||
$html .= '<a href="#" class="assign-me" data-target-id="form-owner_id" data-current-id="'.$this->userSession->getId().'" title="'.t('Assign to me').'" aria-label="'.t('Assign to me').'">'.t('Me').'</a>';
|
||||
$html .= '</small>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderCategoryField(array $categories, array $values, array $errors = array(), array $attributes = array(), $allow_one_item = false)
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="6"'), $attributes);
|
||||
$html = '';
|
||||
|
||||
if (! (! $allow_one_item && count($categories) === 1 && key($categories) == 0)) {
|
||||
$html .= $this->helper->form->label(t('Category'), 'category_id');
|
||||
$html .= $this->helper->form->select('category_id', $categories, $values, $errors, $attributes);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderSwimlaneField(array $swimlanes, array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="7"'), $attributes);
|
||||
$html = '';
|
||||
|
||||
if (count($swimlanes) > 1) {
|
||||
$html .= $this->helper->form->label(t('Swimlane'), 'swimlane_id');
|
||||
$html .= $this->helper->form->select('swimlane_id', $swimlanes, $values, $errors, $attributes);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderColumnField(array $columns, array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="8"'), $attributes);
|
||||
|
||||
$html = $this->helper->form->label(t('Column'), 'column_id');
|
||||
$html .= $this->helper->form->select('column_id', $columns, $values, $errors, $attributes);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderPriorityField(array $project, array $values)
|
||||
{
|
||||
$range = range($project['priority_start'], $project['priority_end']);
|
||||
$options = array_combine($range, $range);
|
||||
$values += array('priority' => $project['priority_default']);
|
||||
|
||||
$html = $this->helper->form->label(t('Priority'), 'priority');
|
||||
$html .= $this->helper->form->select('priority', $options, $values, array(), array('tabindex="9"'));
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderScoreField(array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="14"'), $attributes);
|
||||
|
||||
$html = $this->helper->form->label(t('Complexity'), 'score');
|
||||
$html .= $this->helper->form->number('score', $values, $errors, $attributes);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderReferenceField(array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="15"'), $attributes);
|
||||
|
||||
$html = $this->helper->form->label(t('Reference'), 'reference');
|
||||
$html .= $this->helper->form->text('reference', $values, $errors, $attributes, 'form-input-small');
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderTimeEstimatedField(array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="12"'), $attributes);
|
||||
|
||||
$html = $this->helper->form->label(t('Original estimate'), 'time_estimated');
|
||||
$html .= $this->helper->form->numeric('time_estimated', $values, $errors, $attributes);
|
||||
$html .= ' '.t('hours');
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderTimeSpentField(array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="13"'), $attributes);
|
||||
|
||||
$html = $this->helper->form->label(t('Time spent'), 'time_spent');
|
||||
$html .= $this->helper->form->numeric('time_spent', $values, $errors, $attributes);
|
||||
$html .= ' '.t('hours');
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderStartDateField(array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="11"'), $attributes);
|
||||
return $this->helper->form->datetime(t('Start Date'), 'date_started', $values, $errors, $attributes);
|
||||
}
|
||||
|
||||
public function renderDueDateField(array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$attributes = array_merge(array('tabindex="10"'), $attributes);
|
||||
return $this->helper->form->datetime(t('Due Date'), 'date_due', $values, $errors, $attributes);
|
||||
}
|
||||
|
||||
public function renderPriority($priority)
|
||||
{
|
||||
$html = '<span class="task-priority" title="'.t('Task priority').'">';
|
||||
$html .= '<span class="ui-helper-hidden-accessible">'.t('Task priority').' </span>';
|
||||
$html .= $this->helper->text->e($priority >= 0 ? 'P'.$priority : '-P'.abs($priority));
|
||||
$html .= '</span>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderReference(array $task)
|
||||
{
|
||||
if (! empty($task['reference'])) {
|
||||
$reference = $this->helper->text->e($task['reference']);
|
||||
|
||||
if (filter_var($task['reference'], FILTER_VALIDATE_URL) !== false) {
|
||||
return sprintf('<a href="%s" target=_blank">%s</a>', $reference, $reference);
|
||||
}
|
||||
|
||||
return $reference;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function renderInternalLinkField(array $internallinks, array $values, array $errors = array(), array $attributes = array())
|
||||
{
|
||||
$html = '';
|
||||
|
||||
$html .= $this->helper->form->hidden('opposite_task_id', $values);
|
||||
$html .= '<span class="task-internallink" title="'.t('Add internal link').'">'.t('Add internal link').'</span>';
|
||||
$html .= '<br><br>';
|
||||
$html .= $this->helper->form->label(t('Label'), 'link_id');
|
||||
$html .= $this->helper->form->select('link_id', $internallinks, $values, $errors, $attributes);
|
||||
|
||||
$html .= $this->helper->form->label(t('Task'), 'title');
|
||||
$html .= $this->helper->form->text(
|
||||
'title',
|
||||
$values,
|
||||
$errors,
|
||||
array(
|
||||
'required',
|
||||
'placeholder="'.t('Start to type task title...').'"',
|
||||
'title="'.t('Start to type task title...').'"',
|
||||
'data-dst-field="opposite_task_id"',
|
||||
'data-search-url="'.$this->helper->url->href('TaskAjaxController', 'autocomplete', array('exclude_task_ids' => $values['task_ids'])).'"',
|
||||
),
|
||||
'autocomplete'
|
||||
);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderFileUpload($screenshot = '', array $files = array())
|
||||
{
|
||||
$upload_max_size = get_upload_max_size()*0.90; // 10% margin for the orther part of request + conversion in base64
|
||||
$html = '<div class="task-form-bottom-column">';
|
||||
$html .= ' <div id="screenshot-zone">';
|
||||
$html .= ' <p id="screenshot-inner">'.t('Take a screenshot and press CTRL+V or ⌘+V to paste here.').'</p>';
|
||||
$html .= ' </div>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="task-form-bottom-column">';
|
||||
$html .= $this->helper->app->component('file-upload-task-create', array(
|
||||
'maxSize' => $upload_max_size,
|
||||
'labelDropzone' => t('Drag and drop your files here'),
|
||||
'labelOr' => t('or'),
|
||||
'labelChooseFiles' => t('choose files'),
|
||||
'labelOversize' => t('The total maximum allowed attachments size is %sB.', $this->helper->text->bytes($upload_max_size)),
|
||||
'labelSuccess' => t('All files have been uploaded successfully.'),
|
||||
'labelCloseSuccess' => t('Close this window'),
|
||||
'labelUploadError' => t('Unable to upload this file.'),
|
||||
'screenshot' => $screenshot,
|
||||
'files' => $files,
|
||||
));
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function getProgress($task)
|
||||
{
|
||||
if (! isset($this->columns[$task['project_id']])) {
|
||||
$this->columns[$task['project_id']] = $this->columnModel->getList($task['project_id']);
|
||||
}
|
||||
|
||||
return $this->taskModel->getProgress($task, $this->columns[$task['project_id']]);
|
||||
}
|
||||
|
||||
public function getNewBoardTaskButton(array $swimlane, array $column)
|
||||
{
|
||||
$html = '<div class="board-add-icon">';
|
||||
$providers = $this->externalTaskManager->getProviders();
|
||||
|
||||
if (empty($providers)) {
|
||||
$html .= $this->helper->modal->largeIcon(
|
||||
'plus',
|
||||
t('Add a new task'),
|
||||
'TaskCreationController',
|
||||
'show',
|
||||
array(
|
||||
'project_id' => $column['project_id'],
|
||||
'column_id' => $column['id'],
|
||||
'swimlane_id' => $swimlane['id'],
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$html .= '<div class="dropdown">';
|
||||
$html .= '<a href="#" class="dropdown-menu"><i class="fa fa-plus" aria-hidden="true"></i></a><ul>';
|
||||
|
||||
$link = $this->helper->modal->large(
|
||||
'plus',
|
||||
t('Add a new Kanboard task'),
|
||||
'TaskCreationController',
|
||||
'show',
|
||||
array(
|
||||
'project_id' => $column['project_id'],
|
||||
'column_id' => $column['id'],
|
||||
'swimlane_id' => $swimlane['id'],
|
||||
)
|
||||
);
|
||||
|
||||
$html .= '<li>'.$link.'</li>';
|
||||
|
||||
foreach ($providers as $provider) {
|
||||
$link = $this->helper->url->link(
|
||||
$provider->getMenuAddLabel(),
|
||||
'ExternalTaskCreationController',
|
||||
'step1',
|
||||
array('project_id' => $column['project_id'], 'swimlane_id' => $swimlane['id'], 'column_id' => $column['id'], 'provider_name' => $provider->getName()),
|
||||
false,
|
||||
'js-modal-large'
|
||||
);
|
||||
|
||||
$html .= '<li>'.$provider->getIcon().' '.$link.'</li>';
|
||||
}
|
||||
|
||||
$html .= '</ul></div>';
|
||||
}
|
||||
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Markdown;
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Text Helpers
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TextHelper extends Base
|
||||
{
|
||||
/**
|
||||
* HTML escaping
|
||||
*
|
||||
* @param string $value Value to escape
|
||||
* @return string
|
||||
*/
|
||||
public function e($value)
|
||||
{
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join with HTML escaping
|
||||
*
|
||||
* @param $glue
|
||||
* @param array $list
|
||||
* @return string
|
||||
*/
|
||||
public function implode($glue, array $list)
|
||||
{
|
||||
array_walk($list, function (&$value) {
|
||||
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8', false);
|
||||
});
|
||||
return implode($glue, $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Markdown transformation
|
||||
*
|
||||
* @param string $text
|
||||
* @param boolean $isPublicLink
|
||||
* @return string
|
||||
*/
|
||||
public function markdown($text, $isPublicLink = false)
|
||||
{
|
||||
$parser = new Markdown($this->container, $isPublicLink);
|
||||
$parser->setSafeMode(true);
|
||||
$parser->setMarkupEscaped(MARKDOWN_ESCAPE_HTML);
|
||||
$parser->setBreaksEnabled(true);
|
||||
return $parser->text($text ?: '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply transformation
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $text
|
||||
* @return string
|
||||
*/
|
||||
public function reply($username, $text)
|
||||
{
|
||||
$res = t('%s wrote: ', $username).PHP_EOL.'> ';
|
||||
|
||||
$lines = preg_split("/\r\n|\n|\r/", $text);
|
||||
|
||||
return $res.implode(PHP_EOL.'> ', $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a file size
|
||||
*
|
||||
* @param integer $size Size in bytes
|
||||
* @param integer $precision Precision
|
||||
* @return string
|
||||
*/
|
||||
public function bytes($size, $precision = 2)
|
||||
{
|
||||
if ($size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$base = log($size) / log(1024);
|
||||
$suffixes = array('', 'k', 'M', 'G', 'T');
|
||||
|
||||
return round(pow(1024, $base - floor($base)), $precision).$suffixes[(int)floor($base)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if needle is contained in the haystack
|
||||
*
|
||||
* @param string $haystack Haystack
|
||||
* @param string $needle Needle
|
||||
* @return boolean
|
||||
*/
|
||||
public function contains($haystack, $needle)
|
||||
{
|
||||
return strpos($haystack, $needle) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value from a dictionary
|
||||
*
|
||||
* @param mixed $id Key
|
||||
* @param array $listing Dictionary
|
||||
* @param string $default_value Value displayed when the key doesn't exists
|
||||
* @return string
|
||||
*/
|
||||
public function in($id, array $listing, $default_value = '?')
|
||||
{
|
||||
if (isset($listing[$id])) {
|
||||
return $this->helper->text->e($listing[$id]);
|
||||
}
|
||||
|
||||
return $default_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* Url Helper
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class UrlHelper extends Base
|
||||
{
|
||||
private $base = '';
|
||||
private $directory = '';
|
||||
|
||||
/**
|
||||
* Helper to generate a link to the documentation
|
||||
*
|
||||
* @access public
|
||||
* @param string $label
|
||||
* @param string $file
|
||||
* @return string
|
||||
*/
|
||||
public function doc($label, $file = '')
|
||||
{
|
||||
$url = sprintf(DOCUMENTATION_URL_PATTERN, $file);
|
||||
return sprintf('<a href="%s" target="_blank">%s</a>', $url, $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Button Link Element
|
||||
*
|
||||
* @access public
|
||||
* @param string $icon Font-Awesome icon
|
||||
* @param string $label Link label
|
||||
* @param string $controller Controller name
|
||||
* @param string $action Action name
|
||||
* @param array $params Url parameters
|
||||
* @param string $class CSS class attribute
|
||||
* @return string
|
||||
*/
|
||||
public function button($icon, $label, $controller, $action, array $params = array(), $class = '')
|
||||
{
|
||||
$html = '<i class="fa fa-'.$icon.' fa-fw"></i> '.$label;
|
||||
$class = 'btn '.$class;
|
||||
return $this->link($html, $controller, $action, $params, false, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link element with icon
|
||||
*
|
||||
* @access public
|
||||
* @param string $icon Icon name
|
||||
* @param string $label Link label
|
||||
* @param string $controller Controller name
|
||||
* @param string $action Action name
|
||||
* @param array $params Url parameters
|
||||
* @param boolean $csrf Add a CSRF token
|
||||
* @param string $class CSS class attribute
|
||||
* @param string $title Link title
|
||||
* @param boolean $newTab Open the link in a new tab
|
||||
* @param string $anchor Link Anchor
|
||||
* @param bool $absolute
|
||||
* @return string
|
||||
*/
|
||||
public function icon($icon, $label, $controller, $action, array $params = array(), $csrf = false, $class = '', $title = '', $newTab = false, $anchor = '', $absolute = false)
|
||||
{
|
||||
$html = '<i class="fa fa-fw fa-'.$icon.'" aria-hidden="true"></i>'.$label;
|
||||
return $this->helper->url->link($html, $controller, $action, $params, $csrf, $class, $title, $newTab, $anchor, $absolute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link element
|
||||
*
|
||||
* @access public
|
||||
* @param string $label Link label
|
||||
* @param string $controller Controller name
|
||||
* @param string $action Action name
|
||||
* @param array $params Url parameters
|
||||
* @param boolean $csrf Add a CSRF token
|
||||
* @param string $class CSS class attribute
|
||||
* @param string $title Link title
|
||||
* @param boolean $newTab Open the link in a new tab
|
||||
* @param string $anchor Link Anchor
|
||||
* @param bool $absolute
|
||||
* @return string
|
||||
*/
|
||||
public function link($label, $controller, $action, array $params = array(), $csrf = false, $class = '', $title = '', $newTab = false, $anchor = '', $absolute = false)
|
||||
{
|
||||
return '<a href="'.$this->href($controller, $action, $params, $csrf, $anchor, $absolute).'" class="'.$class.'" title=\''.$title.'\' '.($newTab ? 'target="_blank"' : '').'>'.$label.'</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute link
|
||||
*
|
||||
* @param string $label
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
public function absoluteLink($label, $controller, $action, array $params = array())
|
||||
{
|
||||
return $this->link($label, $controller, $action, $params, false, '', '', true, '', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML Hyperlink
|
||||
*
|
||||
* @access public
|
||||
* @param string $controller Controller name
|
||||
* @param string $action Action name
|
||||
* @param array $params Url parameters
|
||||
* @param boolean $csrf Add a CSRF token
|
||||
* @param string $anchor Link Anchor
|
||||
* @param boolean $absolute Absolute or relative link
|
||||
* @return string
|
||||
*/
|
||||
public function href($controller, $action, array $params = array(), $csrf = false, $anchor = '', $absolute = false)
|
||||
{
|
||||
return $this->build('&', $controller, $action, $params, $csrf, $anchor, $absolute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate controller/action url
|
||||
*
|
||||
* @access public
|
||||
* @param string $controller Controller name
|
||||
* @param string $action Action name
|
||||
* @param array $params Url parameters
|
||||
* @param string $anchor Link Anchor
|
||||
* @param boolean $absolute Absolute or relative link
|
||||
* @return string
|
||||
*/
|
||||
public function to($controller, $action, array $params = array(), $anchor = '', $absolute = false)
|
||||
{
|
||||
return $this->build('&', $controller, $action, $params, false, $anchor, $absolute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get application base url
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function base()
|
||||
{
|
||||
if (empty($this->base)) {
|
||||
$this->base = $this->configModel->get('application_url') ?: 'http://localhost/';
|
||||
}
|
||||
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get application base directory
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function dir()
|
||||
{
|
||||
if ($this->directory === '' && $this->request->getMethod() !== '') {
|
||||
if (defined('KANBOARD_URL') && strlen(KANBOARD_URL) > 0) {
|
||||
$this->directory = parse_url(KANBOARD_URL, PHP_URL_PATH);
|
||||
} else {
|
||||
$this->directory = str_replace('\\', '/', dirname($this->request->getServerVariable('PHP_SELF')));
|
||||
$this->directory = $this->directory !== '/' ? $this->directory.'/' : '/';
|
||||
$this->directory = str_replace('//', '/', $this->directory);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build relative url
|
||||
*
|
||||
* @access protected
|
||||
* @param string $separator Querystring argument separator
|
||||
* @param string $controller Controller name
|
||||
* @param string $action Action name
|
||||
* @param array $params Url parameters
|
||||
* @param boolean $csrf Add a CSRF token
|
||||
* @param string $anchor Link Anchor
|
||||
* @param boolean $absolute Absolute or relative link
|
||||
* @return string
|
||||
*/
|
||||
protected function build($separator, $controller, $action, array $params = array(), $csrf = false, $anchor = '', $absolute = false)
|
||||
{
|
||||
$path = $this->route->findUrl($controller, $action, $params);
|
||||
$qs = array();
|
||||
|
||||
if (empty($path)) {
|
||||
$qs['controller'] = $controller;
|
||||
$qs['action'] = $action;
|
||||
$qs += $params;
|
||||
} else {
|
||||
unset($params['plugin']);
|
||||
}
|
||||
|
||||
if ($csrf) {
|
||||
$qs['csrf_token'] = $this->token->getCSRFToken();
|
||||
}
|
||||
|
||||
if (! empty($qs)) {
|
||||
$path .= '?'.http_build_query($qs, '', $separator);
|
||||
}
|
||||
|
||||
return ($absolute ? $this->base() : $this->dir()).$path.(empty($anchor) ? '' : '#'.$anchor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Helper;
|
||||
|
||||
use Kanboard\Core\Base;
|
||||
|
||||
/**
|
||||
* User helpers
|
||||
*
|
||||
* @package helper
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class UserHelper extends Base
|
||||
{
|
||||
public function getTheme()
|
||||
{
|
||||
return $this->userSession->getTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return subtask list toggle value
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasSubtaskListActivated()
|
||||
{
|
||||
return $this->userSession->hasSubtaskListActivated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the logged user has unread notifications
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasNotifications()
|
||||
{
|
||||
return $this->userUnreadNotificationModel->hasNotifications($this->userSession->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get initials from a user
|
||||
*
|
||||
* @access public
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
public function getInitials($name)
|
||||
{
|
||||
$initials = '';
|
||||
|
||||
foreach (explode(' ', $name, 2) as $string) {
|
||||
$initials .= mb_substr($string, 0, 1, 'UTF-8');
|
||||
}
|
||||
|
||||
return mb_strtoupper($initials, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the user full name
|
||||
*
|
||||
* @param array $user User properties
|
||||
* @return string
|
||||
*/
|
||||
public function getFullname(array $user = array())
|
||||
{
|
||||
$user = empty($user) ? $this->userSession->getAll() : $user;
|
||||
return $user['name'] ?: $user['username'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user id
|
||||
*
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->userSession->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given user_id is the connected user
|
||||
*
|
||||
* @param integer $user_id User id
|
||||
* @return boolean
|
||||
*/
|
||||
public function isCurrentUser($user_id)
|
||||
{
|
||||
return $this->userSession->getId() == $user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the logged user is admin
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function isAdmin()
|
||||
{
|
||||
return $this->userSession->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get role
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getRole()
|
||||
{
|
||||
return $this->userSession->getRole();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get role name
|
||||
*
|
||||
* @access public
|
||||
* @param string $role
|
||||
* @return string
|
||||
*/
|
||||
public function getRoleName($role = '')
|
||||
{
|
||||
return $this->role->getRoleName($role ?: $this->userSession->getRole());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group names for a given user and return an associative array:
|
||||
*
|
||||
* @access public
|
||||
* @param integer $userID User id
|
||||
* @return array
|
||||
*/
|
||||
public function getUsersGroupNames($userID)
|
||||
{
|
||||
$groupsList = array_column($this->groupMemberModel->getGroups($userID), 'name');
|
||||
$limitedList = $groupsList;
|
||||
$total = count($groupsList);
|
||||
|
||||
if ($total > 0 && SHOW_GROUP_MEMBERSHIPS_IN_USERLIST_WITH_LIMIT > 0) {
|
||||
$limitedList = array_slice($groupsList, 0, SHOW_GROUP_MEMBERSHIPS_IN_USERLIST_WITH_LIMIT);
|
||||
}
|
||||
|
||||
return [
|
||||
'full_list' => $groupsList,
|
||||
'limited_list' => $limitedList,
|
||||
'has_groups' => $total > 0,
|
||||
'total' => $total,
|
||||
'shown' => count($limitedList),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check application access
|
||||
*
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAccess($controller, $action)
|
||||
{
|
||||
if (! $this->userSession->isLogged()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$key = 'app_access:'.$controller.$action;
|
||||
$result = $this->memoryCache->get($key);
|
||||
|
||||
if ($result === null) {
|
||||
$result = $this->applicationAuthorization->isAllowed($controller, $action, $this->userSession->getRole());
|
||||
$this->memoryCache->set($key, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check project access
|
||||
*
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @param integer $project_id
|
||||
* @return bool
|
||||
*/
|
||||
public function hasProjectAccess($controller, $action, $project_id)
|
||||
{
|
||||
$key = 'project_access:'.$controller.$action.$project_id;
|
||||
$result = $this->memoryCache->get($key);
|
||||
|
||||
if ($result === null) {
|
||||
$result = $this->helper->projectRole->checkProjectAccess($controller, $action, $project_id);
|
||||
$this->memoryCache->set($key, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user