看板初始化提交

This commit is contained in:
zephyr
2026-06-01 21:23:12 -07:00
commit 54a842f4ab
2104 changed files with 241695 additions and 0 deletions
+209
View File
@@ -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 '';
}
}
+69
View File
@@ -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>';
}
}
+68
View File
@@ -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);
}
}
+27
View File
@@ -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;
}
}
+23
View File
@@ -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);
}
}
+171
View File
@@ -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'));
}
}
+152
View File
@@ -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;
}
}
+482
View File
@@ -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>&nbsp;%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]).'"' : '';
}
}
+104
View File
@@ -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;
}
}
+218
View File
@@ -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'].' &gt; '.$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'].' &gt; '.$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);
}
}
+68
View File
@@ -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;
}
}
+96
View File
@@ -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');
}
}
+94
View File
@@ -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;
}
}
}
}
+104
View File
@@ -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);
}
}
+87
View File
@@ -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;
}
}
+338
View File
@@ -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;
}
}
+174
View File
@@ -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 .= '&nbsp;';
$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 '';
}
}
+380
View File
@@ -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 .= '&nbsp;';
$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;
}
}
+121
View File
@@ -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;
}
}
+214
View File
@@ -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('&amp;', $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);
}
}
+198
View File
@@ -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;
}
}