Merge branch 'master' of https://github.com/ukko/kanboard into 1245_bug_with_subtask_timer

This commit is contained in:
Max Kamashev
2015-09-24 12:07:40 +03:00
36 changed files with 1098 additions and 78 deletions

View File

@@ -126,6 +126,17 @@ abstract class Base extends \Core\Base
return get_called_class();
}
/**
* Get project id
*
* @access public
* @return integer
*/
public function getProjectId()
{
return $this->project_id;
}
/**
* Set an user defined parameter
*

View File

@@ -54,6 +54,8 @@ abstract class Base extends \Core\Base
else if (! $is_user && ! $is_both_procedure && $is_user_procedure) {
throw new AccessDeniedException('Permission denied');
}
$this->logger->debug('API call: '.$procedure);
}
public function checkProjectPermission($project_id)
@@ -70,7 +72,7 @@ abstract class Base extends \Core\Base
}
}
protected function formatTask(array $task)
protected function formatTask($task)
{
if (! empty($task)) {
$task['url'] = $this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), '', true);
@@ -80,7 +82,7 @@ abstract class Base extends \Core\Base
return $task;
}
protected function formatTasks(array $tasks)
protected function formatTasks($tasks)
{
if (! empty($tasks)) {
foreach ($tasks as &$task) {
@@ -91,7 +93,7 @@ abstract class Base extends \Core\Base
return $tasks;
}
protected function formatProject(array $project)
protected function formatProject($project)
{
if (! empty($project)) {
$project['url'] = array(
@@ -104,7 +106,7 @@ abstract class Base extends \Core\Base
return $project;
}
protected function formatProjects(array $projects)
protected function formatProjects($projects)
{
if (! empty($projects)) {
foreach ($projects as &$project) {

View File

@@ -2,6 +2,8 @@
namespace Api;
use Core\ObjectStorage\ObjectStorageException;
/**
* File API controller
*
@@ -22,16 +24,17 @@ class File extends \Core\Base
public function downloadFile($file_id)
{
$file = $this->file->getById($file_id);
try {
if (! empty($file)) {
$file = $this->file->getById($file_id);
$filename = FILES_DIR.$file['path'];
if (file_exists($filename)) {
return base64_encode(file_get_contents($filename));
if (! empty($file)) {
return base64_encode($this->objectStorage->get($file['path']));
}
}
catch (ObjectStorageException $e) {
$this->logger->error($e->getMessage());
}
return '';
}

View File

@@ -2,6 +2,8 @@
namespace Controller;
use Core\ObjectStorage\ObjectStorageException;
/**
* File controller
*
@@ -74,15 +76,21 @@ class File extends Base
*/
public function download()
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
try {
if ($file['task_id'] != $task['id']) {
$this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
if ($file['task_id'] != $task['id']) {
$this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
$this->response->forceDownload($file['name']);
$this->objectStorage->output($file['path']);
}
catch (ObjectStorageException $e) {
$this->logger->error($e->getMessage());
}
$this->response->forceDownload($file['name']);
$this->objectStorage->passthru($file['path']);
}
/**
@@ -110,15 +118,21 @@ class File extends Base
*/
public function image()
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
try {
if ($file['task_id'] != $task['id']) {
$this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
if ($file['task_id'] != $task['id']) {
$this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
$this->response->contentType($this->file->getImageMimeType($file['name']));
$this->objectStorage->output($file['path']);
}
catch (ObjectStorageException $e) {
$this->logger->error($e->getMessage());
}
$this->response->contentType($this->file->getImageMimeType($file['name']));
$this->objectStorage->passthru($file['path']);
}
/**
@@ -128,15 +142,21 @@ class File extends Base
*/
public function thumbnail()
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
try {
if ($file['task_id'] != $task['id']) {
$this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
if ($file['task_id'] != $task['id']) {
$this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
$this->response->contentType('image/jpeg');
$this->objectStorage->output($this->file->getThumbnailPath($file['path']));
}
catch (ObjectStorageException $e) {
$this->logger->error($e->getMessage());
}
$this->response->contentType('image/jpeg');
$this->objectStorage->passthru($this->file->getThumbnailPath($file['path']));
}
/**

View File

@@ -70,7 +70,7 @@ class FileStorage implements ObjectStorageInterface
* @access public
* @param string $key
*/
public function passthru($key)
public function output($key)
{
$filename = $this->path.DIRECTORY_SEPARATOR.$key;

View File

@@ -35,7 +35,7 @@ interface ObjectStorageInterface
* @access public
* @param string $key
*/
public function passthru($key);
public function output($key);
/**
* Move local file to object storage

View File

@@ -28,8 +28,8 @@ class Loader extends \Core\Base
*/
public function scan()
{
if (file_exists(__DIR__.'/../../../plugins')) {
$dir = new DirectoryIterator(__DIR__.'/../../../plugins');
if (file_exists(PLUGINS_DIR)) {
$dir = new DirectoryIterator(PLUGINS_DIR);
foreach ($dir as $fileinfo) {
if (! $fileinfo->isDot() && $fileinfo->isDir()) {
@@ -65,7 +65,7 @@ class Loader extends \Core\Base
*/
public function loadSchema($plugin)
{
$filename = __DIR__.'/../../../plugins/'.$plugin.'/Schema/'.ucfirst(DB_DRIVER).'.php';
$filename = PLUGINS_DIR.'/'.$plugin.'/Schema/'.ucfirst(DB_DRIVER).'.php';
if (file_exists($filename)) {
require_once($filename);

View File

@@ -10,6 +10,26 @@ namespace Helper;
*/
class Hook extends \Core\Base
{
/**
* Add assets JS or CSS
*
* @access public
* @param string $type
* @param string $hook
* @param array $variables
* @return string
*/
public function asset($type, $hook)
{
$buffer = '';
foreach ($this->hook->getListeners($hook) as $file) {
$buffer .= $this->helper->asset->$type($file);
}
return $buffer;
}
/**
* Render all attached hooks
*

View File

@@ -36,7 +36,7 @@ class SlackWebhook extends \Core\Base
}
$options = $this->projectIntegration->getParameters($project_id);
return $options['slack_webhook_url'];
return isset($options['slack_webhook_url']) ? $options['slack_webhook_url'] : '';
}
/**
@@ -52,14 +52,14 @@ class SlackWebhook extends \Core\Base
if (! empty($channel)) {
return $channel;
}
}
$options = $this->projectIntegration->getParameters($project_id);
return $options['slack_webhook_channel'];
$options = $this->projectIntegration->getParameters($project_id);
return isset($options['slack_webhook_channel']) ? $options['slack_webhook_channel'] : '';
}
/**
* Send message to the incoming Slack webhook
* Send notification to Slack
*
* @access public
* @param integer $project_id Project id
@@ -76,23 +76,52 @@ class SlackWebhook extends \Core\Base
$event['event_name'] = $event_name;
$event['author'] = $this->user->getFullname($this->session['user']);
$payload = array(
'text' => '*['.$project['name'].']* '.str_replace('"', '"', $this->projectActivity->getTitle($event)).(isset($event['task']['title']) ? ' ('.$event['task']['title'].')' : ''),
'username' => 'Kanboard',
'icon_url' => 'http://kanboard.net/assets/img/favicon.png',
);
$message = '*['.$project['name'].']* ';
$message .= str_replace('"', '"', $this->projectActivity->getTitle($event));
$message .= isset($event['task']['title']) ? ' ('.$event['task']['title'].')' : '';
if ($this->config->get('application_url')) {
$payload['text'] .= ' - <'.$this->helper->url->href('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id), false, '', true);
$payload['text'] .= '|'.t('view the task on Kanboard').'>';
$message .= ' - <'.$this->helper->url->href('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id), false, '', true);
$message .= '|'.t('view the task on Kanboard').'>';
}
$channel = $this->getChannel($project_id);
if (! empty($channel)) {
$payload['channel'] = $channel;
}
$this->httpClient->postJson($this->getWebhookUrl($project_id), $payload);
$this->sendMessage($project_id, $message);
}
}
/**
* Send message to Slack
*
* @access public
* @param integer $project_id
* @param string $message
*/
public function sendMessage($project_id, $message)
{
$payload = array(
'text' => $message,
'username' => 'Kanboard',
'icon_url' => 'http://kanboard.net/assets/img/favicon.png',
);
$this->sendPayload($project_id, $payload);
}
/**
* Send payload to Slack
*
* @access public
* @param integer $project_id
* @param array $payload
*/
public function sendPayload($project_id, array $payload)
{
$channel = $this->getChannel($project_id);
if (! empty($channel)) {
$payload['channel'] = $channel;
}
$this->httpClient->postJson($this->getWebhookUrl($project_id), $payload);
}
}

View File

@@ -30,6 +30,28 @@ class Action extends Base
*/
const TABLE_PARAMS = 'action_has_params';
/**
* Extended actions
*
* @access private
* @var array
*/
private $actions = array();
/**
* Extend the list of default actions
*
* @access public
* @param string $className
* @param string $description
* @return Action
*/
public function extendActions($className, $description)
{
$this->actions[$className] = $description;
return $this;
}
/**
* Return the name and description of available actions
*
@@ -62,6 +84,8 @@ class Action extends Base
'TaskAssignColorLink' => t('Change task color when using a specific task link'),
);
$values = array_merge($values, $this->actions);
asort($values);
return $values;
@@ -296,7 +320,7 @@ class Action extends Base
*/
public function load($name, $project_id, $event)
{
$className = '\Action\\'.$name;
$className = $name{0} !== '\\' ? '\Action\\'.$name : $name;
return new $className($this->container, $project_id, $event);
}

View File

@@ -54,6 +54,10 @@ class File extends Base
$file = $this->getbyId($file_id);
$this->objectStorage->remove($file['path']);
if ($file['is_image'] == 1) {
$this->objectStorage->remove($this->getThumbnailPath($file['path']));
}
return $this->db->table(self::TABLE)->eq('id', $file['id'])->remove();
}
catch (ObjectStorageException $e) {

View File

@@ -21,6 +21,9 @@
<?= $this->asset->css('assets/css/print.css', true, 'print') ?>
<?= $this->asset->customCss() ?>
<?= $this->hook->asset('css', 'template:layout:css') ?>
<?= $this->hook->asset('js', 'template:layout:js') ?>
<link rel="icon" type="image/png" href="<?= $this->url->dir() ?>assets/img/favicon.png">
<link rel="apple-touch-icon" href="<?= $this->url->dir() ?>assets/img/touch-icon-iphone.png">
<link rel="apple-touch-icon" sizes="72x72" href="<?= $this->url->dir() ?>assets/img/touch-icon-ipad.png">
@@ -44,7 +47,7 @@
<?= $this->render('header', array(
'title' => $title,
'description' => isset($description) ? $description : '',
'board_selector' => $board_selector,
'board_selector' => isset($board_selector) ? $board_selector : array(),
)) ?>
<section class="page">
<?= $this->app->flashMessage() ?>

View File

@@ -29,24 +29,7 @@ if (! extension_loaded('mbstring')) {
die('PHP extension required: mbstring');
}
// Check if /data is writeable
if (! is_writable('data')) {
die('The directory "data" must be writeable by your web server user');
}
// Fix wrong value for arg_separator.output, used by the function http_build_query()
if (ini_get('arg_separator.output') === '&amp;') {
ini_set('arg_separator.output', '&');
}
// Prepare folder for uploaded files
if (! is_dir(FILES_DIR)) {
if (! mkdir(FILES_DIR, 0755, true)) {
die('Unable to create the upload directory: "'.FILES_DIR.'"');
}
}
// Check permissions for files folder
if (! is_writable(FILES_DIR)) {
die('The directory "'.FILES_DIR.'" must be writeable by your webserver user');
}

View File

@@ -4,6 +4,9 @@
defined('DEBUG') or define('DEBUG', false);
defined('DEBUG_FILE') or define('DEBUG_FILE', __DIR__.'/../data/debug.log');
// Plugin directory
defined('PLUGINS_DIR') or define('PLUGINS_DIR', __DIR__.'/../plugins');
// Application version
defined('APP_VERSION') or define('APP_VERSION', 'master');