Fix bugs, improve perfs and use SimpleLogger instead of Monolog
This commit is contained in:
parent
c32567857d
commit
3076ba22dd
|
|
@ -54,4 +54,5 @@ Thumbs.db
|
||||||
################
|
################
|
||||||
config.php
|
config.php
|
||||||
data/files
|
data/files
|
||||||
|
data/cache
|
||||||
vendor
|
vendor
|
||||||
|
|
@ -101,9 +101,13 @@ abstract class Base
|
||||||
public function __destruct()
|
public function __destruct()
|
||||||
{
|
{
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
|
|
||||||
foreach ($this->container['db']->getLogMessages() as $message) {
|
foreach ($this->container['db']->getLogMessages() as $message) {
|
||||||
$this->container['logger']->addDebug($message);
|
$this->container['logger']->debug($message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->container['logger']->debug('SQL_QUERIES={nb}', array('nb' => $this->container['db']->nb_queries));
|
||||||
|
$this->container['logger']->debug('RENDERING={time}', array('time' => microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core;
|
||||||
|
|
||||||
|
use Pimple\Container;
|
||||||
|
|
||||||
|
abstract class Cache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Container instance
|
||||||
|
*
|
||||||
|
* @access protected
|
||||||
|
* @var \Pimple\Container
|
||||||
|
*/
|
||||||
|
protected $container;
|
||||||
|
|
||||||
|
abstract public function init();
|
||||||
|
abstract public function set($key, $value);
|
||||||
|
abstract public function get($key);
|
||||||
|
abstract public function flush();
|
||||||
|
abstract public function remove($key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param \Pimple\Container $container
|
||||||
|
*/
|
||||||
|
public function __construct(Container $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
$this->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy cache
|
||||||
|
*
|
||||||
|
* Note: Arguments must be scalar types
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $container Container name
|
||||||
|
* @param string $method Container method
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function proxy($container, $method)
|
||||||
|
{
|
||||||
|
$args = func_get_args();
|
||||||
|
$key = 'proxy_'.implode('_', $args);
|
||||||
|
$result = $this->get($key);
|
||||||
|
|
||||||
|
if ($result === null) {
|
||||||
|
$result = call_user_func_array(array($this->container[$container], $method), array_splice($args, 2));
|
||||||
|
$this->set($key, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core;
|
||||||
|
|
||||||
|
class FileCache extends Cache
|
||||||
|
{
|
||||||
|
const CACHE_FOLDER = 'data/cache/';
|
||||||
|
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
if (! is_dir(self::CACHE_FOLDER)) {
|
||||||
|
mkdir(self::CACHE_FOLDER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set($key, $value)
|
||||||
|
{
|
||||||
|
file_put_contents(self::CACHE_FOLDER.$key, json_encode($value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get($key)
|
||||||
|
{
|
||||||
|
if (file_exists(self::CACHE_FOLDER.$key)) {
|
||||||
|
return json_decode(file_get_contents(self::CACHE_FOLDER.$key), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flush()
|
||||||
|
{
|
||||||
|
foreach (glob(self::CACHE_FOLDER.'*') as $filename) {
|
||||||
|
@unlink($filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove($key)
|
||||||
|
{
|
||||||
|
@unlink(self::CACHE_FOLDER.$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -48,6 +48,22 @@ class Helper
|
||||||
return $this->container[$name];
|
return $this->container[$name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy cache helper for acl::isManagerActionAllowed()
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param integer $project_id
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function isManager($project_id)
|
||||||
|
{
|
||||||
|
if ($this->userSession->isAdmin()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->container['memoryCache']->proxy('acl', 'isManagerActionAllowed', $project_id);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the user full name
|
* Return the user full name
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core;
|
||||||
|
|
||||||
|
class MemoryCache extends Cache
|
||||||
|
{
|
||||||
|
private $storage = array();
|
||||||
|
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set($key, $value)
|
||||||
|
{
|
||||||
|
$this->storage[$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get($key)
|
||||||
|
{
|
||||||
|
return isset($this->storage[$key]) ? $this->storage[$key] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flush()
|
||||||
|
{
|
||||||
|
$this->storage = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove($key)
|
||||||
|
{
|
||||||
|
unset($this->storage[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => 'Ændre ansvarlig',
|
'Change assignee' => 'Ændre ansvarlig',
|
||||||
'Change assignee for the task "%s"' => 'Ændre ansvarlig for opgaven: "%s"',
|
'Change assignee for the task "%s"' => 'Ændre ansvarlig for opgaven: "%s"',
|
||||||
'Timezone' => 'Tidszone',
|
'Timezone' => 'Tidszone',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'Denne information kunne ikke findes i databasen!',
|
'Sorry, I didn\'t find this information in my database!' => 'Denne information kunne ikke findes i databasen!',
|
||||||
'Page not found' => 'Siden er ikke fundet',
|
'Page not found' => 'Siden er ikke fundet',
|
||||||
'Complexity' => 'Kompleksitet',
|
'Complexity' => 'Kompleksitet',
|
||||||
'limit' => 'Begrænsning',
|
'limit' => 'Begrænsning',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => 'Zuständigkeit ändern',
|
'Change assignee' => 'Zuständigkeit ändern',
|
||||||
'Change assignee for the task "%s"' => 'Zuständigkeit für diese Aufgabe ändern: "%s"',
|
'Change assignee for the task "%s"' => 'Zuständigkeit für diese Aufgabe ändern: "%s"',
|
||||||
'Timezone' => 'Zeitzone',
|
'Timezone' => 'Zeitzone',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden!',
|
'Sorry, I didn\'t find this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden!',
|
||||||
'Page not found' => 'Seite nicht gefunden',
|
'Page not found' => 'Seite nicht gefunden',
|
||||||
'Complexity' => 'Komplexität',
|
'Complexity' => 'Komplexität',
|
||||||
'limit' => 'Limit',
|
'limit' => 'Limit',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => 'Cambiar la persona asignada',
|
'Change assignee' => 'Cambiar la persona asignada',
|
||||||
'Change assignee for the task "%s"' => 'Cambiar la persona asignada por la tarea « %s »',
|
'Change assignee for the task "%s"' => 'Cambiar la persona asignada por la tarea « %s »',
|
||||||
'Timezone' => 'Zona horaria',
|
'Timezone' => 'Zona horaria',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'Lo siento no he encontrado información en la base de datos!',
|
'Sorry, I didn\'t find this information in my database!' => 'Lo siento no he encontrado información en la base de datos!',
|
||||||
'Page not found' => 'Página no encontrada',
|
'Page not found' => 'Página no encontrada',
|
||||||
'Complexity' => 'Complejidad',
|
'Complexity' => 'Complejidad',
|
||||||
'limit' => 'límite',
|
'limit' => 'límite',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => 'Vaihda suorittajaa',
|
'Change assignee' => 'Vaihda suorittajaa',
|
||||||
'Change assignee for the task "%s"' => 'Vaihda suorittajaa tehtävälle %s',
|
'Change assignee for the task "%s"' => 'Vaihda suorittajaa tehtävälle %s',
|
||||||
'Timezone' => 'Aikavyöhyke',
|
'Timezone' => 'Aikavyöhyke',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'Anteeksi, en löytänyt tätä tietoa tietokannastani',
|
'Sorry, I didn\'t find this information in my database!' => 'Anteeksi, en löytänyt tätä tietoa tietokannastani',
|
||||||
'Page not found' => 'Sivua ei löydy',
|
'Page not found' => 'Sivua ei löydy',
|
||||||
'Complexity' => 'Monimutkaisuus',
|
'Complexity' => 'Monimutkaisuus',
|
||||||
'limit' => 'raja',
|
'limit' => 'raja',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => 'Changer la personne assignée',
|
'Change assignee' => 'Changer la personne assignée',
|
||||||
'Change assignee for the task "%s"' => 'Changer la personne assignée pour la tâche « %s »',
|
'Change assignee for the task "%s"' => 'Changer la personne assignée pour la tâche « %s »',
|
||||||
'Timezone' => 'Fuseau horaire',
|
'Timezone' => 'Fuseau horaire',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'Désolé, je n\'ai pas trouvé cette information dans ma base de données !',
|
'Sorry, I didn\'t find this information in my database!' => 'Désolé, je n\'ai pas trouvé cette information dans ma base de données !',
|
||||||
'Page not found' => 'Page introuvable',
|
'Page not found' => 'Page introuvable',
|
||||||
'Complexity' => 'Complexité',
|
'Complexity' => 'Complexité',
|
||||||
'limit' => 'limite',
|
'limit' => 'limite',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => 'Felelős módosítása',
|
'Change assignee' => 'Felelős módosítása',
|
||||||
'Change assignee for the task "%s"' => 'Feladat felelősének módosítása: "%s"',
|
'Change assignee for the task "%s"' => 'Feladat felelősének módosítása: "%s"',
|
||||||
'Timezone' => 'Időzóna',
|
'Timezone' => 'Időzóna',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'Ez az információ nem található az adatbázisban!',
|
'Sorry, I didn\'t find this information in my database!' => 'Ez az információ nem található az adatbázisban!',
|
||||||
'Page not found' => 'Az oldal nem található',
|
'Page not found' => 'Az oldal nem található',
|
||||||
'Complexity' => 'Bonyolultság',
|
'Complexity' => 'Bonyolultság',
|
||||||
'limit' => 'határ',
|
'limit' => 'határ',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => 'Cambiare la persona assegnata',
|
'Change assignee' => 'Cambiare la persona assegnata',
|
||||||
'Change assignee for the task "%s"' => 'Cambiare la persona assegnata per il compito « %s »',
|
'Change assignee for the task "%s"' => 'Cambiare la persona assegnata per il compito « %s »',
|
||||||
'Timezone' => 'Fuso orario',
|
'Timezone' => 'Fuso orario',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'Mi dispiace, non ho trovato questa informazione sulla base dati!',
|
'Sorry, I didn\'t find this information in my database!' => 'Mi dispiace, non ho trovato questa informazione sulla base dati!',
|
||||||
'Page not found' => 'Pagina non trovata',
|
'Page not found' => 'Pagina non trovata',
|
||||||
// 'Complexity' => '',
|
// 'Complexity' => '',
|
||||||
'limit' => 'limite',
|
'limit' => 'limite',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => '担当を変更する',
|
'Change assignee' => '担当を変更する',
|
||||||
'Change assignee for the task "%s"' => 'タスク「%s」の担当を変更する',
|
'Change assignee for the task "%s"' => 'タスク「%s」の担当を変更する',
|
||||||
'Timezone' => 'タイムゾーン',
|
'Timezone' => 'タイムゾーン',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'データベース上で情報が見つかりませんでした!',
|
'Sorry, I didn\'t find this information in my database!' => 'データベース上で情報が見つかりませんでした!',
|
||||||
'Page not found' => 'ページが見つかりません',
|
'Page not found' => 'ページが見つかりません',
|
||||||
'Complexity' => '複雑さ',
|
'Complexity' => '複雑さ',
|
||||||
'limit' => '制限',
|
'limit' => '制限',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => 'Zmień odpowiedzialną osobę',
|
'Change assignee' => 'Zmień odpowiedzialną osobę',
|
||||||
'Change assignee for the task "%s"' => 'Zmień odpowiedzialną osobę dla zadania "%s"',
|
'Change assignee for the task "%s"' => 'Zmień odpowiedzialną osobę dla zadania "%s"',
|
||||||
'Timezone' => 'Strefa czasowa',
|
'Timezone' => 'Strefa czasowa',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'Niestety nie znaleziono tej informacji w bazie danych',
|
'Sorry, I didn\'t find this information in my database!' => 'Niestety nie znaleziono tej informacji w bazie danych',
|
||||||
'Page not found' => 'Strona nie istnieje',
|
'Page not found' => 'Strona nie istnieje',
|
||||||
'Complexity' => 'Poziom trudności',
|
'Complexity' => 'Poziom trudności',
|
||||||
'limit' => 'limit',
|
'limit' => 'limit',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => 'Mudar a designação',
|
'Change assignee' => 'Mudar a designação',
|
||||||
'Change assignee for the task "%s"' => 'Modificar designação para a tarefa "%s"',
|
'Change assignee for the task "%s"' => 'Modificar designação para a tarefa "%s"',
|
||||||
'Timezone' => 'Fuso horário',
|
'Timezone' => 'Fuso horário',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'Desculpe, não encontrei esta informação no meu banco de dados!',
|
'Sorry, I didn\'t find this information in my database!' => 'Desculpe, não encontrei esta informação no meu banco de dados!',
|
||||||
'Page not found' => 'Página não encontrada',
|
'Page not found' => 'Página não encontrada',
|
||||||
'Complexity' => 'Complexidade',
|
'Complexity' => 'Complexidade',
|
||||||
'limit' => 'limite',
|
'limit' => 'limite',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => 'Сменить назначенного',
|
'Change assignee' => 'Сменить назначенного',
|
||||||
'Change assignee for the task "%s"' => 'Сменить назначенного для задачи « %s »',
|
'Change assignee for the task "%s"' => 'Сменить назначенного для задачи « %s »',
|
||||||
'Timezone' => 'Часовой пояс',
|
'Timezone' => 'Часовой пояс',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'К сожалению, информация в базе данных не найдена !',
|
'Sorry, I didn\'t find this information in my database!' => 'К сожалению, информация в базе данных не найдена !',
|
||||||
'Page not found' => 'Страница не найдена',
|
'Page not found' => 'Страница не найдена',
|
||||||
'Complexity' => 'Сложность',
|
'Complexity' => 'Сложность',
|
||||||
'limit' => 'лимит',
|
'limit' => 'лимит',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => 'Ändra uppdragsinnehavare',
|
'Change assignee' => 'Ändra uppdragsinnehavare',
|
||||||
'Change assignee for the task "%s"' => 'Ändra uppdragsinnehavare för uppgiften "%s"',
|
'Change assignee for the task "%s"' => 'Ändra uppdragsinnehavare för uppgiften "%s"',
|
||||||
'Timezone' => 'Tidszon',
|
'Timezone' => 'Tidszon',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'Informationen kunde inte hittas i databasen.',
|
'Sorry, I didn\'t find this information in my database!' => 'Informationen kunde inte hittas i databasen.',
|
||||||
'Page not found' => 'Sidan hittas inte',
|
'Page not found' => 'Sidan hittas inte',
|
||||||
'Complexity' => 'Ungefärligt antal timmar',
|
'Complexity' => 'Ungefärligt antal timmar',
|
||||||
'limit' => 'max',
|
'limit' => 'max',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => 'เปลี่ยนการกำหนด',
|
'Change assignee' => 'เปลี่ยนการกำหนด',
|
||||||
'Change assignee for the task "%s"' => 'เปลี่ยนการกำหนดสำหรับงาน « %s »',
|
'Change assignee for the task "%s"' => 'เปลี่ยนการกำหนดสำหรับงาน « %s »',
|
||||||
'Timezone' => 'เขตเวลา',
|
'Timezone' => 'เขตเวลา',
|
||||||
'Sorry, I didn\'t found this information in my database!' => 'เสียใจด้วย ไม่สามารถหาข้อมูลในฐานข้อมูลได้',
|
'Sorry, I didn\'t find this information in my database!' => 'เสียใจด้วย ไม่สามารถหาข้อมูลในฐานข้อมูลได้',
|
||||||
'Page not found' => 'ไม่พบหน้า',
|
'Page not found' => 'ไม่พบหน้า',
|
||||||
'Complexity' => 'ความซับซ้อน',
|
'Complexity' => 'ความซับซ้อน',
|
||||||
'limit' => 'จำกัด',
|
'limit' => 'จำกัด',
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ return array(
|
||||||
'Change assignee' => '变更负责人',
|
'Change assignee' => '变更负责人',
|
||||||
'Change assignee for the task "%s"' => '更改任务"%s"的负责人',
|
'Change assignee for the task "%s"' => '更改任务"%s"的负责人',
|
||||||
'Timezone' => '时区',
|
'Timezone' => '时区',
|
||||||
'Sorry, I didn\'t found this information in my database!' => '抱歉,无法在数据库中找到该信息!',
|
'Sorry, I didn\'t find this information in my database!' => '抱歉,无法在数据库中找到该信息!',
|
||||||
'Page not found' => '页面未找到',
|
'Page not found' => '页面未找到',
|
||||||
'Complexity' => '复杂度',
|
'Complexity' => '复杂度',
|
||||||
'limit' => '限制',
|
'limit' => '限制',
|
||||||
|
|
|
||||||
|
|
@ -189,10 +189,6 @@ class Acl extends Base
|
||||||
|
|
||||||
public function isManagerActionAllowed($project_id)
|
public function isManagerActionAllowed($project_id)
|
||||||
{
|
{
|
||||||
if ($this->userSession->isAdmin()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $project_id > 0 && $this->projectPermission->isManager($project_id, $this->userSession->getId());
|
return $project_id > 0 && $this->projectPermission->isManager($project_id, $this->userSession->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,7 @@ class Action extends Base
|
||||||
*/
|
*/
|
||||||
public function remove($action_id)
|
public function remove($action_id)
|
||||||
{
|
{
|
||||||
|
// $this->container['fileCache']->remove('proxy_action_getAll');
|
||||||
return $this->db->table(self::TABLE)->eq('id', $action_id)->remove();
|
return $this->db->table(self::TABLE)->eq('id', $action_id)->remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -242,6 +243,8 @@ class Action extends Base
|
||||||
|
|
||||||
$this->db->closeTransaction();
|
$this->db->closeTransaction();
|
||||||
|
|
||||||
|
// $this->container['fileCache']->remove('proxy_action_getAll');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,7 +255,10 @@ class Action extends Base
|
||||||
*/
|
*/
|
||||||
public function attachEvents()
|
public function attachEvents()
|
||||||
{
|
{
|
||||||
foreach ($this->getAll() as $action) {
|
//$actions = $this->container['fileCache']->proxy('action', 'getAll');
|
||||||
|
$actions = $this->getAll();
|
||||||
|
|
||||||
|
foreach ($actions as $action) {
|
||||||
|
|
||||||
$listener = $this->load($action['action_name'], $action['project_id'], $action['event_name']);
|
$listener = $this->load($action['action_name'], $action['project_id'], $action['event_name']);
|
||||||
|
|
||||||
|
|
@ -315,6 +321,8 @@ class Action extends Base
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $this->container['fileCache']->remove('proxy_action_getAll');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -253,6 +253,23 @@ class Board extends Base
|
||||||
return $swimlanes;
|
return $swimlanes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total of tasks per column
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param integer $project_id
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getColumnStats($project_id)
|
||||||
|
{
|
||||||
|
return $this->db
|
||||||
|
->table(Task::TABLE)
|
||||||
|
->eq('project_id', $project_id)
|
||||||
|
->eq('is_active', 1)
|
||||||
|
->groupBy('column_id')
|
||||||
|
->listing('column_id', 'COUNT(*) AS total');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the first column id for a given project
|
* Get the first column id for a given project
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ class Notification extends Base
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Swift_TransportException $e) {
|
catch (Swift_TransportException $e) {
|
||||||
$this->container['logger']->addError($e->getMessage());
|
$this->container['logger']->error($e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -191,11 +191,12 @@ class Project extends Base
|
||||||
public function getStats($project_id)
|
public function getStats($project_id)
|
||||||
{
|
{
|
||||||
$stats = array();
|
$stats = array();
|
||||||
$columns = $this->board->getColumns($project_id);
|
|
||||||
$stats['nb_active_tasks'] = 0;
|
$stats['nb_active_tasks'] = 0;
|
||||||
|
$columns = $this->board->getColumns($project_id);
|
||||||
|
$column_stats = $this->board->getColumnStats($project_id);
|
||||||
|
|
||||||
foreach ($columns as &$column) {
|
foreach ($columns as &$column) {
|
||||||
$column['nb_active_tasks'] = $this->taskFinder->countByColumnId($project_id, $column['id']);
|
$column['nb_active_tasks'] = isset($column_stats[$column['id']]) ? $column_stats[$column['id']] : 0;
|
||||||
$stats['nb_active_tasks'] += $column['nb_active_tasks'];
|
$stats['nb_active_tasks'] += $column['nb_active_tasks'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,10 @@ class ProjectPaginator extends Base
|
||||||
foreach ($projects as &$project) {
|
foreach ($projects as &$project) {
|
||||||
|
|
||||||
$project['columns'] = $this->board->getColumns($project['id']);
|
$project['columns'] = $this->board->getColumns($project['id']);
|
||||||
|
$stats = $this->board->getColumnStats($project['id']);
|
||||||
|
|
||||||
foreach ($project['columns'] as &$column) {
|
foreach ($project['columns'] as &$column) {
|
||||||
$column['nb_tasks'] = $this->taskFinder->countByColumnId($project['id'], $column['id']);
|
$column['nb_tasks'] = isset($stats[$column['id']]) ? $stats[$column['id']] : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -298,7 +298,11 @@ class ProjectPermission extends Base
|
||||||
*/
|
*/
|
||||||
public function getAllowedProjects($user_id)
|
public function getAllowedProjects($user_id)
|
||||||
{
|
{
|
||||||
return $this->filterProjects($this->project->getListByStatus(Project::ACTIVE), $user_id, 'isUserAllowed');
|
if ($this->user->isAdmin($user_id)) {
|
||||||
|
return $this->project->getListByStatus(Project::ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getMemberProjects($user_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -310,7 +314,11 @@ class ProjectPermission extends Base
|
||||||
*/
|
*/
|
||||||
public function getMemberProjects($user_id)
|
public function getMemberProjects($user_id)
|
||||||
{
|
{
|
||||||
return $this->filterProjects($this->project->getListByStatus(Project::ACTIVE), $user_id, 'isMember');
|
return $this->db
|
||||||
|
->table(Project::TABLE)
|
||||||
|
->eq('user_id', $user_id)
|
||||||
|
->join(self::TABLE, 'project_id', 'id')
|
||||||
|
->listing('projects.id', 'name');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -216,16 +216,15 @@ class TaskFinder extends Base
|
||||||
* @access public
|
* @access public
|
||||||
* @param integer $project_id Project id
|
* @param integer $project_id Project id
|
||||||
* @param integer $column_id Column id
|
* @param integer $column_id Column id
|
||||||
* @param array $status List of status id
|
|
||||||
* @return integer
|
* @return integer
|
||||||
*/
|
*/
|
||||||
public function countByColumnId($project_id, $column_id, array $status = array(Task::STATUS_OPEN))
|
public function countByColumnId($project_id, $column_id)
|
||||||
{
|
{
|
||||||
return $this->db
|
return $this->db
|
||||||
->table(Task::TABLE)
|
->table(Task::TABLE)
|
||||||
->eq('project_id', $project_id)
|
->eq('project_id', $project_id)
|
||||||
->eq('column_id', $column_id)
|
->eq('column_id', $column_id)
|
||||||
->in('is_active', $status)
|
->in('is_active', 1)
|
||||||
->count();
|
->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,8 @@ class User extends Base
|
||||||
*/
|
*/
|
||||||
public function isAdmin($user_id)
|
public function isAdmin($user_id)
|
||||||
{
|
{
|
||||||
return $this->db
|
return $this->userSession->isAdmin() || // Avoid SQL query if connected
|
||||||
|
$this->db
|
||||||
->table(User::TABLE)
|
->table(User::TABLE)
|
||||||
->eq('id', $user_id)
|
->eq('id', $user_id)
|
||||||
->eq('is_admin', 1)
|
->eq('is_admin', 1)
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ class ClassProvider implements ServiceProviderInterface
|
||||||
'Core' => array(
|
'Core' => array(
|
||||||
'Template',
|
'Template',
|
||||||
'Session',
|
'Session',
|
||||||
|
'MemoryCache',
|
||||||
|
'FileCache',
|
||||||
),
|
),
|
||||||
'Integration' => array(
|
'Integration' => array(
|
||||||
'GitlabWebhook',
|
'GitlabWebhook',
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ class DatabaseProvider implements ServiceProviderInterface
|
||||||
{
|
{
|
||||||
$container['db'] = $this->getInstance();
|
$container['db'] = $this->getInstance();
|
||||||
$container['db']->stopwatch = DEBUG;
|
$container['db']->stopwatch = DEBUG;
|
||||||
|
$container['db']->log_queries = DEBUG;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,19 @@ namespace ServiceProvider;
|
||||||
|
|
||||||
use Pimple\Container;
|
use Pimple\Container;
|
||||||
use Pimple\ServiceProviderInterface;
|
use Pimple\ServiceProviderInterface;
|
||||||
use Monolog\Logger;
|
use SimpleLogger\Logger;
|
||||||
use Monolog\Handler\StreamHandler;
|
use SimpleLogger\Syslog;
|
||||||
use Monolog\Handler\SyslogHandler;
|
use SimpleLogger\File;
|
||||||
|
|
||||||
class LoggingProvider implements ServiceProviderInterface
|
class LoggingProvider implements ServiceProviderInterface
|
||||||
{
|
{
|
||||||
public function register(Container $container)
|
public function register(Container $container)
|
||||||
{
|
{
|
||||||
$logger = new Logger('app');
|
$logger = new Logger;
|
||||||
$logger->pushHandler(new SyslogHandler('kanboard', LOG_USER, Logger::INFO));
|
$logger->setLogger(new Syslog('kanboard'));
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
$logger->pushHandler(new StreamHandler(__DIR__.'/../../data/debug.log', Logger::DEBUG));
|
$logger->setLogger(new File(__DIR__.'/../../data/debug.log'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$container['logger'] = $logger;
|
$container['logger'] = $logger;
|
||||||
|
|
|
||||||
|
|
@ -12,25 +12,22 @@ class MailerProvider implements ServiceProviderInterface
|
||||||
{
|
{
|
||||||
public function register(Container $container)
|
public function register(Container $container)
|
||||||
{
|
{
|
||||||
$container['mailer'] = $this->getInstance();
|
$container['mailer'] = function () {
|
||||||
}
|
switch (MAIL_TRANSPORT) {
|
||||||
|
case 'smtp':
|
||||||
|
$transport = Swift_SmtpTransport::newInstance(MAIL_SMTP_HOSTNAME, MAIL_SMTP_PORT);
|
||||||
|
$transport->setUsername(MAIL_SMTP_USERNAME);
|
||||||
|
$transport->setPassword(MAIL_SMTP_PASSWORD);
|
||||||
|
$transport->setEncryption(MAIL_SMTP_ENCRYPTION);
|
||||||
|
break;
|
||||||
|
case 'sendmail':
|
||||||
|
$transport = Swift_SendmailTransport::newInstance(MAIL_SENDMAIL_COMMAND);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$transport = Swift_MailTransport::newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
public function getInstance()
|
return $transport;
|
||||||
{
|
};
|
||||||
switch (MAIL_TRANSPORT) {
|
|
||||||
case 'smtp':
|
|
||||||
$transport = Swift_SmtpTransport::newInstance(MAIL_SMTP_HOSTNAME, MAIL_SMTP_PORT);
|
|
||||||
$transport->setUsername(MAIL_SMTP_USERNAME);
|
|
||||||
$transport->setPassword(MAIL_SMTP_PASSWORD);
|
|
||||||
$transport->setEncryption(MAIL_SMTP_ENCRYPTION);
|
|
||||||
break;
|
|
||||||
case 'sendmail':
|
|
||||||
$transport = Swift_SendmailTransport::newInstance(MAIL_SENDMAIL_COMMAND);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$transport = Swift_MailTransport::newInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $transport;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<section id="main">
|
<section id="main">
|
||||||
<p class="alert alert-error">
|
<p class="alert alert-error">
|
||||||
<?= t('Sorry, I didn\'t found this information in my database!') ?>
|
<?= t('Sorry, I didn\'t find this information in my database!') ?>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<?= $this->a('#'.$project['id'], 'board', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link') ?>
|
<?= $this->a('#'.$project['id'], 'board', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link') ?>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?php if ($this->projectPermission->isManager($project['id'], $this->userSession->getId())): ?>
|
<?php if ($this->isManager($project['id'])): ?>
|
||||||
<?= $this->a('<i class="fa fa-cog"></i>', 'project', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Settings')) ?>
|
<?= $this->a('<i class="fa fa-cog"></i>', 'project', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Settings')) ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<?= $this->a($this->e($project['name']), 'board', 'show', array('project_id' => $project['id'])) ?>
|
<?= $this->a($this->e($project['name']), 'board', 'show', array('project_id' => $project['id'])) ?>
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<div class="task-board-title">
|
<div class="task-board-title">
|
||||||
<?= $this->a($this->e($task['title']), 'task', 'show', array('task_id' => $task['id']), false, '', t('View this task')) ?>
|
<?= $this->a($this->e($task['title']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, '', t('View this task')) ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<?= $this->a(t('Summary'), 'project', 'show', array('project_id' => $project['id'])) ?>
|
<?= $this->a(t('Summary'), 'project', 'show', array('project_id' => $project['id'])) ?>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<?php if ($this->acl->isManagerActionAllowed($project['id'])): ?>
|
<?php if ($this->isManager($project['id'])): ?>
|
||||||
<li>
|
<li>
|
||||||
<?= $this->a(t('Public access'), 'project', 'share', array('project_id' => $project['id'])) ?>
|
<?= $this->a(t('Public access'), 'project', 'share', array('project_id' => $project['id'])) ?>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@
|
||||||
"erusev/parsedown": "1.1.1",
|
"erusev/parsedown": "1.1.1",
|
||||||
"lusitanian/oauth": "0.3.5",
|
"lusitanian/oauth": "0.3.5",
|
||||||
"pimple/pimple": "~3.0",
|
"pimple/pimple": "~3.0",
|
||||||
"monolog/monolog": "1.11.0",
|
|
||||||
"symfony/console": "@stable",
|
"symfony/console": "@stable",
|
||||||
"symfony/event-dispatcher": "~2.6"
|
"symfony/event-dispatcher": "~2.6",
|
||||||
|
"fguillot/simpleLogger": "dev-master"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-0": {"": "app/"},
|
"psr-0": {"": "app/"},
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"hash": "b36eeeb06a0ff9d55f2342792bd6e880",
|
"hash": "32ca2365366d59b6b6f4fc2b5af435a7",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "erusev/parsedown",
|
"name": "erusev/parsedown",
|
||||||
|
|
@ -88,12 +88,12 @@
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/fguillot/picoDb.git",
|
"url": "https://github.com/fguillot/picoDb.git",
|
||||||
"reference": "682616b9accbfd719677ed0b3f478107cea2dacc"
|
"reference": "3ee555da2e2bda42b5d6aa9b231b534fd69db96c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/fguillot/picoDb/zipball/682616b9accbfd719677ed0b3f478107cea2dacc",
|
"url": "https://api.github.com/repos/fguillot/picoDb/zipball/3ee555da2e2bda42b5d6aa9b231b534fd69db96c",
|
||||||
"reference": "682616b9accbfd719677ed0b3f478107cea2dacc",
|
"reference": "3ee555da2e2bda42b5d6aa9b231b534fd69db96c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -117,7 +117,7 @@
|
||||||
],
|
],
|
||||||
"description": "Minimalist database query builder",
|
"description": "Minimalist database query builder",
|
||||||
"homepage": "https://github.com/fguillot/picoDb",
|
"homepage": "https://github.com/fguillot/picoDb",
|
||||||
"time": "2014-12-31 17:44:58"
|
"time": "2015-01-02 22:00:06"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fguillot/simple-validator",
|
"name": "fguillot/simple-validator",
|
||||||
|
|
@ -156,6 +156,43 @@
|
||||||
"homepage": "https://github.com/fguillot/simpleValidator",
|
"homepage": "https://github.com/fguillot/simpleValidator",
|
||||||
"time": "2014-11-25 22:58:14"
|
"time": "2014-11-25 22:58:14"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "fguillot/simpleLogger",
|
||||||
|
"version": "dev-master",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/fguillot/simpleLogger.git",
|
||||||
|
"reference": "81df5643931d97e0101b4757d9454dbcb13a3fa9"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/fguillot/simpleLogger/zipball/81df5643931d97e0101b4757d9454dbcb13a3fa9",
|
||||||
|
"reference": "81df5643931d97e0101b4757d9454dbcb13a3fa9",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0",
|
||||||
|
"psr/log": "~1.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"SimpleLogger": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Frédéric Guillot"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP library to write logs (compatible with PSR-3)",
|
||||||
|
"homepage": "https://github.com/fguillot/simpleLogger",
|
||||||
|
"time": "2015-01-02 03:40:21"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ircmaxell/password-compat",
|
"name": "ircmaxell/password-compat",
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
|
|
@ -257,78 +294,6 @@
|
||||||
],
|
],
|
||||||
"time": "2014-09-05 15:19:58"
|
"time": "2014-09-05 15:19:58"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "monolog/monolog",
|
|
||||||
"version": "1.11.0",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/Seldaek/monolog.git",
|
|
||||||
"reference": "ec3961874c43840e96da3a8a1ed20d8c73d7e5aa"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/ec3961874c43840e96da3a8a1ed20d8c73d7e5aa",
|
|
||||||
"reference": "ec3961874c43840e96da3a8a1ed20d8c73d7e5aa",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=5.3.0",
|
|
||||||
"psr/log": "~1.0"
|
|
||||||
},
|
|
||||||
"provide": {
|
|
||||||
"psr/log-implementation": "1.0.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"aws/aws-sdk-php": "~2.4, >2.4.8",
|
|
||||||
"doctrine/couchdb": "~1.0@dev",
|
|
||||||
"graylog2/gelf-php": "~1.0",
|
|
||||||
"phpunit/phpunit": "~3.7.0",
|
|
||||||
"raven/raven": "~0.5",
|
|
||||||
"ruflin/elastica": "0.90.*",
|
|
||||||
"videlalvaro/php-amqplib": "~2.4"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
|
|
||||||
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
|
|
||||||
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
|
|
||||||
"ext-mongo": "Allow sending log messages to a MongoDB server",
|
|
||||||
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
|
|
||||||
"raven/raven": "Allow sending log messages to a Sentry server",
|
|
||||||
"rollbar/rollbar": "Allow sending log messages to Rollbar",
|
|
||||||
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
|
|
||||||
"videlalvaro/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "1.11.x-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Monolog\\": "src/Monolog"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Jordi Boggiano",
|
|
||||||
"email": "j.boggiano@seld.be",
|
|
||||||
"homepage": "http://seld.be"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
|
|
||||||
"homepage": "http://github.com/Seldaek/monolog",
|
|
||||||
"keywords": [
|
|
||||||
"log",
|
|
||||||
"logging",
|
|
||||||
"psr-3"
|
|
||||||
],
|
|
||||||
"time": "2014-09-30 13:30:58"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "pimple/pimple",
|
"name": "pimple/pimple",
|
||||||
"version": "v3.0.0",
|
"version": "v3.0.0",
|
||||||
|
|
@ -637,7 +602,8 @@
|
||||||
"swiftmailer/swiftmailer": 0,
|
"swiftmailer/swiftmailer": 0,
|
||||||
"fguillot/json-rpc": 20,
|
"fguillot/json-rpc": 20,
|
||||||
"fguillot/picodb": 20,
|
"fguillot/picodb": 20,
|
||||||
"symfony/console": 0
|
"symfony/console": 0,
|
||||||
|
"fguillot/simplelogger": 20
|
||||||
},
|
},
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue