summaryrefslogtreecommitdiff
path: root/app/Model
diff options
context:
space:
mode:
Diffstat (limited to 'app/Model')
-rw-r--r--app/Model/Acl.php13
-rw-r--r--app/Model/Budget.php214
-rw-r--r--app/Model/File.php244
-rw-r--r--app/Model/HourlyRate.php121
-rw-r--r--app/Model/Subtask.php41
-rw-r--r--app/Model/SubtaskTimeTracking.php19
6 files changed, 166 insertions, 486 deletions
diff --git a/app/Model/Acl.php b/app/Model/Acl.php
index 8c28cb1a..9a227cf5 100644
--- a/app/Model/Acl.php
+++ b/app/Model/Acl.php
@@ -64,7 +64,6 @@ class Acl extends Base
'export' => '*',
'project' => array('edit', 'update', 'share', 'integration', 'users', 'alloweverybody', 'allow', 'setowner', 'revoke', 'duplicate', 'disable', 'enable'),
'swimlane' => '*',
- 'budget' => '*',
'gantt' => array('project', 'savetaskdate', 'task', 'savetask'),
);
@@ -95,6 +94,18 @@ class Acl extends Base
);
/**
+ * Extend ACL rules
+ *
+ * @access public
+ * @param string $acl_name
+ * @param aray $rules
+ */
+ public function extend($acl_name, array $rules)
+ {
+ $this->$acl_name = array_merge($this->$acl_name, $rules);
+ }
+
+ /**
* Return true if the specified controller/action match the given acl
*
* @access public
diff --git a/app/Model/Budget.php b/app/Model/Budget.php
deleted file mode 100644
index 76c42ca9..00000000
--- a/app/Model/Budget.php
+++ /dev/null
@@ -1,214 +0,0 @@
-<?php
-
-namespace Model;
-
-use DateInterval;
-use DateTime;
-use SimpleValidator\Validator;
-use SimpleValidator\Validators;
-
-/**
- * Budget
- *
- * @package model
- * @author Frederic Guillot
- */
-class Budget extends Base
-{
- /**
- * SQL table name
- *
- * @var string
- */
- const TABLE = 'budget_lines';
-
- /**
- * Get all budget lines for a project
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getAll($project_id)
- {
- return $this->db->table(self::TABLE)->eq('project_id', $project_id)->desc('date')->findAll();
- }
-
- /**
- * Get the current total of the budget
- *
- * @access public
- * @param integer $project_id
- * @return float
- */
- public function getTotal($project_id)
- {
- $result = $this->db->table(self::TABLE)->columns('SUM(amount) as total')->eq('project_id', $project_id)->findOne();
- return isset($result['total']) ? (float) $result['total'] : 0;
- }
-
- /**
- * Get breakdown by tasks/subtasks/users
- *
- * @access public
- * @param integer $project_id
- * @return \PicoDb\Table
- */
- public function getSubtaskBreakdown($project_id)
- {
- return $this->db
- ->table(SubtaskTimeTracking::TABLE)
- ->columns(
- SubtaskTimeTracking::TABLE.'.id',
- SubtaskTimeTracking::TABLE.'.user_id',
- SubtaskTimeTracking::TABLE.'.subtask_id',
- SubtaskTimeTracking::TABLE.'.start',
- SubtaskTimeTracking::TABLE.'.time_spent',
- Subtask::TABLE.'.task_id',
- Subtask::TABLE.'.title AS subtask_title',
- Task::TABLE.'.title AS task_title',
- Task::TABLE.'.project_id',
- User::TABLE.'.username',
- User::TABLE.'.name'
- )
- ->join(Subtask::TABLE, 'id', 'subtask_id')
- ->join(Task::TABLE, 'id', 'task_id', Subtask::TABLE)
- ->join(User::TABLE, 'id', 'user_id')
- ->eq(Task::TABLE.'.project_id', $project_id)
- ->callback(array($this, 'applyUserRate'));
- }
-
- /**
- * Gather necessary information to display the budget graph
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getDailyBudgetBreakdown($project_id)
- {
- $out = array();
- $in = $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->gt('amount', 0)->asc('date')->getAll('date', 'amount');
- $time_slots = $this->getSubtaskBreakdown($project_id)->findAll();
-
- foreach ($time_slots as $slot) {
- $date = date('Y-m-d', $slot['start']);
-
- if (! isset($out[$date])) {
- $out[$date] = 0;
- }
-
- $out[$date] += $slot['cost'];
- }
-
- $start = key($in) ?: key($out);
- $end = new DateTime;
- $left = 0;
- $serie = array();
-
- for ($today = new DateTime($start); $today <= $end; $today->add(new DateInterval('P1D'))) {
-
- $date = $today->format('Y-m-d');
- $today_in = isset($in[$date]) ? (int) $in[$date] : 0;
- $today_out = isset($out[$date]) ? (int) $out[$date] : 0;
-
- if ($today_in > 0 || $today_out > 0) {
-
- $left += $today_in;
- $left -= $today_out;
-
- $serie[] = array(
- 'date' => $date,
- 'in' => $today_in,
- 'out' => -$today_out,
- 'left' => $left,
- );
- }
- }
-
- return $serie;
- }
-
- /**
- * Filter callback to apply the rate according to the effective date
- *
- * @access public
- * @param array $records
- * @return array
- */
- public function applyUserRate(array $records)
- {
- $rates = $this->hourlyRate->getAllByProject($records[0]['project_id']);
-
- foreach ($records as &$record) {
-
- $hourly_price = 0;
-
- foreach ($rates as $rate) {
-
- if ($rate['user_id'] == $record['user_id'] && date('Y-m-d', $rate['date_effective']) <= date('Y-m-d', $record['start'])) {
- $hourly_price = $this->currency->getPrice($rate['currency'], $rate['rate']);
- break;
- }
- }
-
- $record['cost'] = $hourly_price * $record['time_spent'];
- }
-
- return $records;
- }
-
- /**
- * Add a new budget line in the database
- *
- * @access public
- * @param integer $project_id
- * @param float $amount
- * @param string $comment
- * @param string $date
- * @return boolean|integer
- */
- public function create($project_id, $amount, $comment, $date = '')
- {
- $values = array(
- 'project_id' => $project_id,
- 'amount' => $amount,
- 'comment' => $comment,
- 'date' => $date ?: date('Y-m-d'),
- );
-
- return $this->persist(self::TABLE, $values);
- }
-
- /**
- * Remove a specific budget line
- *
- * @access public
- * @param integer $budget_id
- * @return boolean
- */
- public function remove($budget_id)
- {
- return $this->db->table(self::TABLE)->eq('id', $budget_id)->remove();
- }
-
- /**
- * Validate creation
- *
- * @access public
- * @param array $values Form values
- * @return array $valid, $errors [0] = Success or not, [1] = List of errors
- */
- public function validateCreation(array $values)
- {
- $v = new Validator($values, array(
- new Validators\Required('project_id', t('Field required')),
- new Validators\Required('amount', t('Field required')),
- ));
-
- return array(
- $v->execute(),
- $v->getErrors()
- );
- }
-} \ No newline at end of file
diff --git a/app/Model/File.php b/app/Model/File.php
index f884e460..7adab42b 100644
--- a/app/Model/File.php
+++ b/app/Model/File.php
@@ -3,6 +3,8 @@
namespace Model;
use Event\FileEvent;
+use Core\Tool;
+use Core\ObjectStorage\ObjectStorageException;
/**
* File model
@@ -47,14 +49,17 @@ class File extends Base
*/
public function remove($file_id)
{
- $file = $this->getbyId($file_id);
+ try {
- if (! empty($file)) {
- @unlink(FILES_DIR.$file['path']);
- return $this->db->table(self::TABLE)->eq('id', $file_id)->remove();
- }
+ $file = $this->getbyId($file_id);
+ $this->objectStorage->remove($file['path']);
- return false;
+ return $this->db->table(self::TABLE)->eq('id', $file['id'])->remove();
+ }
+ catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
@@ -66,11 +71,11 @@ class File extends Base
*/
public function removeAll($task_id)
{
- $files = $this->getAll($task_id);
+ $file_ids = $this->db->table(self::TABLE)->eq('task_id', $task_id)->asc('id')->findAllByColumn('id');
$results = array();
- foreach ($files as $file) {
- $results[] = $this->remove($file['id']);
+ foreach ($file_ids as $file_id) {
+ $results[] = $this->remove($file_id);
}
return ! in_array(false, $results, true);
@@ -196,6 +201,30 @@ class File extends Base
}
/**
+ * Return the image mimetype based on the file extension
+ *
+ * @access public
+ * @param $filename
+ * @return string
+ */
+ public function getImageMimeType($filename)
+ {
+ $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+
+ switch ($extension) {
+ case 'jpeg':
+ case 'jpg':
+ return 'image/jpeg';
+ case 'png':
+ return 'image/png';
+ case 'gif':
+ return 'image/gif';
+ default:
+ return 'image/jpeg';
+ }
+ }
+
+ /**
* Generate the path for a new filename
*
* @access public
@@ -210,6 +239,18 @@ class File extends Base
}
/**
+ * Generate the path for a thumbnails
+ *
+ * @access public
+ * @param string $key Storage key
+ * @return string
+ */
+ public function getThumbnailPath($key)
+ {
+ return 'thumbnails'.DIRECTORY_SEPARATOR.$key;
+ }
+
+ /**
* Handle file upload
*
* @access public
@@ -218,11 +259,13 @@ class File extends Base
* @param string $form_name File form name
* @return bool
*/
- public function upload($project_id, $task_id, $form_name)
+ public function uploadFiles($project_id, $task_id, $form_name)
{
- $results = array();
+ try {
- if (! empty($_FILES[$form_name])) {
+ if (empty($_FILES[$form_name])) {
+ return false;
+ }
foreach ($_FILES[$form_name]['error'] as $key => $error) {
@@ -232,22 +275,27 @@ class File extends Base
$uploaded_filename = $_FILES[$form_name]['tmp_name'][$key];
$destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
- @mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
+ if ($this->isImage($original_filename)) {
+ $this->generateThumbnailFromFile($uploaded_filename, $destination_filename);
+ }
- if (@move_uploaded_file($uploaded_filename, FILES_DIR.$destination_filename)) {
+ $this->objectStorage->moveUploadedFile($uploaded_filename, $destination_filename);
- $results[] = $this->create(
- $task_id,
- $original_filename,
- $destination_filename,
- $_FILES[$form_name]['size'][$key]
- );
- }
+ $this->create(
+ $task_id,
+ $original_filename,
+ $destination_filename,
+ $_FILES[$form_name]['size'][$key]
+ );
}
}
- }
- return ! in_array(false, $results, true);
+ return true;
+ }
+ catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
@@ -261,129 +309,77 @@ class File extends Base
*/
public function uploadScreenshot($project_id, $task_id, $blob)
{
- $data = base64_decode($blob);
-
- if (empty($data)) {
- return false;
- }
-
$original_filename = e('Screenshot taken %s', dt('%B %e, %Y at %k:%M %p', time())).'.png';
- $destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
-
- @mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
- @file_put_contents(FILES_DIR.$destination_filename, $data);
-
- return $this->create(
- $task_id,
- $original_filename,
- $destination_filename,
- strlen($data)
- );
+ return $this->uploadContent($project_id, $task_id, $original_filename, $blob);
}
/**
* Handle file upload (base64 encoded content)
*
* @access public
- * @param integer $project_id Project id
- * @param integer $task_id Task id
- * @param string $filename Filename
- * @param string $blob Base64 encoded image
+ * @param integer $project_id Project id
+ * @param integer $task_id Task id
+ * @param string $original_filename Filename
+ * @param string $blob Base64 encoded file
* @return bool|integer
*/
- public function uploadContent($project_id, $task_id, $filename, $blob)
+ public function uploadContent($project_id, $task_id, $original_filename, $blob)
{
- $data = base64_decode($blob);
+ try {
- if (empty($data)) {
- return false;
- }
+ $data = base64_decode($blob);
- $destination_filename = $this->generatePath($project_id, $task_id, $filename);
+ if (empty($data)) {
+ return false;
+ }
+
+ $destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
+ $this->objectStorage->put($destination_filename, $data);
- @mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
- @file_put_contents(FILES_DIR.$destination_filename, $data);
+ if ($this->isImage($original_filename)) {
+ $this->generateThumbnailFromData($destination_filename, $data);
+ }
- return $this->create(
- $task_id,
- $filename,
- $destination_filename,
- strlen($data)
- );
+ return $this->create(
+ $task_id,
+ $original_filename,
+ $destination_filename,
+ strlen($data)
+ );
+ }
+ catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
- * Generate a jpeg thumbnail from an image (output directly the image)
+ * Generate thumbnail from a blob
*
* @access public
- * @param string $filename Source image
- * @param integer $resize_width Desired image width
- * @param integer $resize_height Desired image height
+ * @param string $destination_filename
+ * @param string $data
*/
- public function generateThumbnail($filename, $resize_width, $resize_height)
+ public function generateThumbnailFromData($destination_filename, &$data)
{
- $metadata = getimagesize($filename);
- $src_width = $metadata[0];
- $src_height = $metadata[1];
- $dst_y = 0;
- $dst_x = 0;
-
- if (empty($metadata['mime'])) {
- return;
- }
-
- if ($resize_width == 0 && $resize_height == 0) {
- $resize_width = 100;
- $resize_height = 100;
- }
+ $temp_filename = tempnam(sys_get_temp_dir(), 'datafile');
- if ($resize_width > 0 && $resize_height == 0) {
- $dst_width = $resize_width;
- $dst_height = floor($src_height * ($resize_width / $src_width));
- $dst_image = imagecreatetruecolor($dst_width, $dst_height);
- }
- elseif ($resize_width == 0 && $resize_height > 0) {
- $dst_width = floor($src_width * ($resize_height / $src_height));
- $dst_height = $resize_height;
- $dst_image = imagecreatetruecolor($dst_width, $dst_height);
- }
- else {
-
- $src_ratio = $src_width / $src_height;
- $resize_ratio = $resize_width / $resize_height;
-
- if ($src_ratio <= $resize_ratio) {
- $dst_width = $resize_width;
- $dst_height = floor($src_height * ($resize_width / $src_width));
-
- $dst_y = ($dst_height - $resize_height) / 2 * (-1);
- }
- else {
- $dst_width = floor($src_width * ($resize_height / $src_height));
- $dst_height = $resize_height;
-
- $dst_x = ($dst_width - $resize_width) / 2 * (-1);
- }
-
- $dst_image = imagecreatetruecolor($resize_width, $resize_height);
- }
-
- switch ($metadata['mime']) {
- case 'image/jpeg':
- case 'image/jpg':
- $src_image = imagecreatefromjpeg($filename);
- break;
- case 'image/png':
- $src_image = imagecreatefrompng($filename);
- break;
- case 'image/gif':
- $src_image = imagecreatefromgif($filename);
- break;
- default:
- return;
- }
+ file_put_contents($temp_filename, $data);
+ $this->generateThumbnailFromFile($temp_filename, $destination_filename);
+ unlink($temp_filename);
+ }
- imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
- imagejpeg($dst_image);
+ /**
+ * Generate thumbnail from a blob
+ *
+ * @access public
+ * @param string $uploaded_filename
+ * @param string $destination_filename
+ */
+ public function generateThumbnailFromFile($uploaded_filename, $destination_filename)
+ {
+ $thumbnail_filename = tempnam(sys_get_temp_dir(), 'thumbnail');
+ Tool::generateThumbnail($uploaded_filename, $thumbnail_filename);
+ $this->objectStorage->moveFile($thumbnail_filename, $this->getThumbnailPath($destination_filename));
}
}
diff --git a/app/Model/HourlyRate.php b/app/Model/HourlyRate.php
deleted file mode 100644
index 1550bdae..00000000
--- a/app/Model/HourlyRate.php
+++ /dev/null
@@ -1,121 +0,0 @@
-<?php
-
-namespace Model;
-
-use SimpleValidator\Validator;
-use SimpleValidator\Validators;
-
-/**
- * Hourly Rate
- *
- * @package model
- * @author Frederic Guillot
- */
-class HourlyRate extends Base
-{
- /**
- * SQL table name
- *
- * @var string
- */
- const TABLE = 'hourly_rates';
-
- /**
- * Get all user rates for a given project
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getAllByProject($project_id)
- {
- $members = $this->projectPermission->getMembers($project_id);
-
- if (empty($members)) {
- return array();
- }
-
- return $this->db->table(self::TABLE)->in('user_id', array_keys($members))->desc('date_effective')->findAll();
- }
-
- /**
- * Get all rates for a given user
- *
- * @access public
- * @param integer $user_id User id
- * @return array
- */
- public function getAllByUser($user_id)
- {
- return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findAll();
- }
-
- /**
- * Get current rate for a given user
- *
- * @access public
- * @param integer $user_id User id
- * @return float
- */
- public function getCurrentRate($user_id)
- {
- return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findOneColumn('rate') ?: 0;
- }
-
- /**
- * Add a new rate in the database
- *
- * @access public
- * @param integer $user_id User id
- * @param float $rate Hourly rate
- * @param string $currency Currency code
- * @param string $date ISO8601 date format
- * @return boolean|integer
- */
- public function create($user_id, $rate, $currency, $date)
- {
- $values = array(
- 'user_id' => $user_id,
- 'rate' => $rate,
- 'currency' => $currency,
- 'date_effective' => $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($date)),
- );
-
- return $this->persist(self::TABLE, $values);
- }
-
- /**
- * Remove a specific rate
- *
- * @access public
- * @param integer $rate_id
- * @return boolean
- */
- public function remove($rate_id)
- {
- return $this->db->table(self::TABLE)->eq('id', $rate_id)->remove();
- }
-
- /**
- * Validate creation
- *
- * @access public
- * @param array $values Form values
- * @return array $valid, $errors [0] = Success or not, [1] = List of errors
- */
- public function validateCreation(array $values)
- {
- $v = new Validator($values, array(
- new Validators\Required('user_id', t('Field required')),
- new Validators\Required('rate', t('Field required')),
- new Validators\Numeric('rate', t('This value must be numeric')),
- new Validators\Required('date_effective', t('Field required')),
- new Validators\Required('currency', t('Field required')),
- ));
-
- return array(
- $v->execute(),
- $v->getErrors()
- );
- }
-}
diff --git a/app/Model/Subtask.php b/app/Model/Subtask.php
index d8a44aff..24508c91 100644
--- a/app/Model/Subtask.php
+++ b/app/Model/Subtask.php
@@ -49,6 +49,7 @@ class Subtask extends Base
*/
const EVENT_UPDATE = 'subtask.update';
const EVENT_CREATE = 'subtask.create';
+ const EVENT_DELETE = 'subtask.delete';
/**
* Get available status
@@ -174,6 +175,23 @@ class Subtask extends Base
}
/**
+ * Prepare data before insert
+ *
+ * @access public
+ * @param array $values Form values
+ */
+ public function prepareCreation(array &$values)
+ {
+ $this->prepare($values);
+
+ $values['position'] = $this->getLastPosition($values['task_id']) + 1;
+ $values['status'] = isset($values['status']) ? $values['status'] : self::STATUS_TODO;
+ $values['time_estimated'] = isset($values['time_estimated']) ? $values['time_estimated'] : 0;
+ $values['time_spent'] = isset($values['time_spent']) ? $values['time_spent'] : 0;
+ $values['user_id'] = isset($values['user_id']) ? $values['user_id'] : 0;
+ }
+
+ /**
* Get the position of the last column for a given project
*
* @access public
@@ -198,9 +216,7 @@ class Subtask extends Base
*/
public function create(array $values)
{
- $this->prepare($values);
- $values['position'] = $this->getLastPosition($values['task_id']) + 1;
-
+ $this->prepareCreation($values);
$subtask_id = $this->persist(self::TABLE, $values);
if ($subtask_id) {
@@ -223,14 +239,13 @@ class Subtask extends Base
public function update(array $values)
{
$this->prepare($values);
+ $subtask = $this->getById($values['id']);
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
if ($result) {
-
- $this->container['dispatcher']->dispatch(
- self::EVENT_UPDATE,
- new SubtaskEvent($values)
- );
+ $event = $subtask;
+ $event['changes'] = array_diff_assoc($values, $subtask);
+ $this->container['dispatcher']->dispatch(self::EVENT_UPDATE, new SubtaskEvent($event));
}
return $result;
@@ -302,7 +317,6 @@ class Subtask extends Base
$positions = array_flip($subtasks);
if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] < count($subtasks)) {
-
$position = ++$subtasks[$subtask_id];
$subtasks[$positions[$position]]--;
@@ -402,7 +416,14 @@ class Subtask extends Base
*/
public function remove($subtask_id)
{
- return $this->db->table(self::TABLE)->eq('id', $subtask_id)->remove();
+ $subtask = $this->getById($subtask_id);
+ $result = $this->db->table(self::TABLE)->eq('id', $subtask_id)->remove();
+
+ if ($result) {
+ $this->container['dispatcher']->dispatch(self::EVENT_DELETE, new SubtaskEvent($subtask));
+ }
+
+ return $result;
}
/**
diff --git a/app/Model/SubtaskTimeTracking.php b/app/Model/SubtaskTimeTracking.php
index 997031e8..56998769 100644
--- a/app/Model/SubtaskTimeTracking.php
+++ b/app/Model/SubtaskTimeTracking.php
@@ -339,20 +339,7 @@ class SubtaskTimeTracking extends Base
*/
public function updateTaskTimeTracking($task_id)
{
- $result = $this->calculateSubtaskTime($task_id);
- $values = array();
-
- if ($result['total_spent'] > 0) {
- $values['time_spent'] = $result['total_spent'];
- }
-
- if ($result['total_estimated'] > 0) {
- $values['time_estimated'] = $result['total_estimated'];
- }
-
- if (empty($values)) {
- return true;
- }
+ $values = $this->calculateSubtaskTime($task_id);
return $this->db
->table(Task::TABLE)
@@ -373,8 +360,8 @@ class SubtaskTimeTracking extends Base
->table(Subtask::TABLE)
->eq('task_id', $task_id)
->columns(
- 'SUM(time_spent) AS total_spent',
- 'SUM(time_estimated) AS total_estimated'
+ 'SUM(time_spent) AS time_spent',
+ 'SUM(time_estimated) AS time_estimated'
)
->findOne();
}