summaryrefslogtreecommitdiff
path: root/app/Core
diff options
context:
space:
mode:
Diffstat (limited to 'app/Core')
-rw-r--r--app/Core/Base.php112
-rw-r--r--app/Core/Cache.php58
-rw-r--r--app/Core/Cli.php75
-rw-r--r--app/Core/EmailClient.php46
-rw-r--r--app/Core/Event.php164
-rw-r--r--app/Core/Helper.php60
-rw-r--r--app/Core/HttpClient.php117
-rw-r--r--app/Core/Listener.php21
-rw-r--r--app/Core/Loader.php62
-rw-r--r--app/Core/Markdown.php47
-rw-r--r--app/Core/MemoryCache.php32
-rw-r--r--app/Core/Paginator.php461
-rw-r--r--app/Core/Registry.php83
-rw-r--r--app/Core/Request.php25
-rw-r--r--app/Core/Response.php38
-rw-r--r--app/Core/Router.php24
-rw-r--r--app/Core/Session.php64
-rw-r--r--app/Core/Template.php14
-rw-r--r--app/Core/Tool.php31
-rw-r--r--app/Core/Translator.php34
20 files changed, 1077 insertions, 491 deletions
diff --git a/app/Core/Base.php b/app/Core/Base.php
new file mode 100644
index 00000000..6cb87cbc
--- /dev/null
+++ b/app/Core/Base.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Core;
+
+use Pimple\Container;
+
+/**
+ * Base class
+ *
+ * @package core
+ * @author Frederic Guillot
+ *
+ * @property \Core\Helper $helper
+ * @property \Core\EmailClient $emailClient
+ * @property \Core\HttpClient $httpClient
+ * @property \Core\Paginator $paginator
+ * @property \Core\Request $request
+ * @property \Core\Session $session
+ * @property \Core\Template $template
+ * @property \Integration\BitbucketWebhook $bitbucketWebhook
+ * @property \Integration\GithubWebhook $githubWebhook
+ * @property \Integration\GitlabWebhook $gitlabWebhook
+ * @property \Integration\HipchatWebhook $hipchatWebhook
+ * @property \Integration\Jabber $jabber
+ * @property \Integration\Mailgun $mailgun
+ * @property \Integration\Postmark $postmark
+ * @property \Integration\SendgridWebhook $sendgridWebhook
+ * @property \Integration\SlackWebhook $slackWebhook
+ * @property \Integration\Smtp $smtp
+ * @property \Model\Acl $acl
+ * @property \Model\Action $action
+ * @property \Model\Authentication $authentication
+ * @property \Model\Board $board
+ * @property \Model\Budget $budget
+ * @property \Model\Category $category
+ * @property \Model\Color $color
+ * @property \Model\Comment $comment
+ * @property \Model\Config $config
+ * @property \Model\Currency $currency
+ * @property \Model\DateParser $dateParser
+ * @property \Model\File $file
+ * @property \Model\HourlyRate $hourlyRate
+ * @property \Model\LastLogin $lastLogin
+ * @property \Model\Link $link
+ * @property \Model\Notification $notification
+ * @property \Model\Project $project
+ * @property \Model\ProjectActivity $projectActivity
+ * @property \Model\ProjectAnalytic $projectAnalytic
+ * @property \Model\ProjectDuplication $projectDuplication
+ * @property \Model\ProjectDailySummary $projectDailySummary
+ * @property \Model\ProjectIntegration $projectIntegration
+ * @property \Model\ProjectPermission $projectPermission
+ * @property \Model\Subtask $subtask
+ * @property \Model\SubtaskExport $subtaskExport
+ * @property \Model\SubtaskForecast $subtaskForecast
+ * @property \Model\SubtaskTimeTracking $subtaskTimeTracking
+ * @property \Model\Swimlane $swimlane
+ * @property \Model\Task $task
+ * @property \Model\TaskCreation $taskCreation
+ * @property \Model\TaskDuplication $taskDuplication
+ * @property \Model\TaskExport $taskExport
+ * @property \Model\TaskFinder $taskFinder
+ * @property \Model\TaskFilter $taskFilter
+ * @property \Model\TaskLink $taskLink
+ * @property \Model\TaskModification $taskModification
+ * @property \Model\TaskPermission $taskPermission
+ * @property \Model\TaskPosition $taskPosition
+ * @property \Model\TaskStatus $taskStatus
+ * @property \Model\TaskValidator $taskValidator
+ * @property \Model\Timetable $timetable
+ * @property \Model\TimetableDay $timetableDay
+ * @property \Model\TimetableExtra $timetableExtra
+ * @property \Model\TimetableOff $timetableOff
+ * @property \Model\TimetableWeek $timetableWeek
+ * @property \Model\Transition $transition
+ * @property \Model\User $user
+ * @property \Model\UserSession $userSession
+ * @property \Model\Webhook $webhook
+ */
+abstract class Base
+{
+ /**
+ * Container instance
+ *
+ * @access protected
+ * @var \Pimple\Container
+ */
+ protected $container;
+
+ /**
+ * Constructor
+ *
+ * @access public
+ * @param \Pimple\Container $container
+ */
+ public function __construct(Container $container)
+ {
+ $this->container = $container;
+ }
+
+ /**
+ * Load automatically models
+ *
+ * @access public
+ * @param string $name Model name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->container[$name];
+ }
+}
diff --git a/app/Core/Cache.php b/app/Core/Cache.php
new file mode 100644
index 00000000..670a76e0
--- /dev/null
+++ b/app/Core/Cache.php
@@ -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;
+ }
+}
diff --git a/app/Core/Cli.php b/app/Core/Cli.php
deleted file mode 100644
index 13533b9a..00000000
--- a/app/Core/Cli.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-
-namespace Core;
-
-use Closure;
-
-/**
- * CLI class
- *
- * @package core
- * @author Frederic Guillot
- */
-class Cli
-{
- /**
- * Default command name
- *
- * @access public
- * @var string
- */
- public $default_command = 'help';
-
- /**
- * List of registered commands
- *
- * @access private
- * @var array
- */
- private $commands = array();
-
- /**
- *
- *
- * @access public
- * @param string $command Command name
- * @param Closure $callback Command callback
- */
- public function register($command, Closure $callback)
- {
- $this->commands[$command] = $callback;
- }
-
- /**
- * Execute a command
- *
- * @access public
- * @param string $command Command name
- */
- public function call($command)
- {
- if (isset($this->commands[$command])) {
- $this->commands[$command]();
- exit;
- }
- }
-
- /**
- * Determine which command to execute
- *
- * @access public
- */
- public function execute()
- {
- if (php_sapi_name() !== 'cli') {
- die('This script work only from the command line.');
- }
-
- if ($GLOBALS['argc'] === 1) {
- $this->call($this->default_command);
- }
-
- $this->call($GLOBALS['argv'][1]);
- $this->call($this->default_command);
- }
-}
diff --git a/app/Core/EmailClient.php b/app/Core/EmailClient.php
new file mode 100644
index 00000000..07687c42
--- /dev/null
+++ b/app/Core/EmailClient.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Core;
+
+/**
+ * Mail client
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+class EmailClient extends Base
+{
+ /**
+ * Send a HTML email
+ *
+ * @access public
+ * @param string $email
+ * @param string $name
+ * @param string $subject
+ * @param string $html
+ */
+ public function send($email, $name, $subject, $html)
+ {
+ $this->container['logger']->debug('Sending email to '.$email.' ('.MAIL_TRANSPORT.')');
+
+ $start_time = microtime(true);
+ $author = 'Kanboard';
+
+ if (Session::isOpen() && $this->userSession->isLogged()) {
+ $author = e('%s via Kanboard', $this->user->getFullname($this->session['user']));
+ }
+
+ switch (MAIL_TRANSPORT) {
+ case 'mailgun':
+ $this->mailgun->sendEmail($email, $name, $subject, $html, $author);
+ break;
+ case 'postmark':
+ $this->postmark->sendEmail($email, $name, $subject, $html, $author);
+ break;
+ default:
+ $this->smtp->sendEmail($email, $name, $subject, $html, $author);
+ }
+
+ $this->container['logger']->debug('Email sent in '.round(microtime(true) - $start_time, 6).' seconds');
+ }
+}
diff --git a/app/Core/Event.php b/app/Core/Event.php
deleted file mode 100644
index a32499d8..00000000
--- a/app/Core/Event.php
+++ /dev/null
@@ -1,164 +0,0 @@
-<?php
-
-namespace Core;
-
-/**
- * Event dispatcher class
- *
- * @package core
- * @author Frederic Guillot
- */
-class Event
-{
- /**
- * Contains all listeners
- *
- * @access private
- * @var array
- */
- private $listeners = array();
-
- /**
- * The last listener executed
- *
- * @access private
- * @var string
- */
- private $lastListener = '';
-
- /**
- * The last triggered event
- *
- * @access private
- * @var string
- */
- private $lastEvent = '';
-
- /**
- * Triggered events list
- *
- * @access private
- * @var array
- */
- private $events = array();
-
- /**
- * Attach a listener object to an event
- *
- * @access public
- * @param string $eventName Event name
- * @param Listener $listener Object that implements the Listener interface
- */
- public function attach($eventName, Listener $listener)
- {
- if (! isset($this->listeners[$eventName])) {
- $this->listeners[$eventName] = array();
- }
-
- $this->listeners[$eventName][] = $listener;
- }
-
- /**
- * Trigger an event
- *
- * @access public
- * @param string $eventName Event name
- * @param array $data Event data
- */
- public function trigger($eventName, array $data)
- {
- if (! $this->isEventTriggered($eventName)) {
-
- $this->events[] = $eventName;
-
- if (isset($this->listeners[$eventName])) {
-
- foreach ($this->listeners[$eventName] as $listener) {
-
- $this->lastEvent = $eventName;
-
- if ($listener->execute($data)) {
- $this->lastListener = get_class($listener);
- }
- }
- }
- }
- }
-
- /**
- * Get the last listener executed
- *
- * @access public
- * @return string Event name
- */
- public function getLastListenerExecuted()
- {
- return $this->lastListener;
- }
-
- /**
- * Get the last fired event
- *
- * @access public
- * @return string Event name
- */
- public function getLastTriggeredEvent()
- {
- return $this->lastEvent;
- }
-
- /**
- * Get a list of triggered events
- *
- * @access public
- * @return array
- */
- public function getTriggeredEvents()
- {
- return $this->events;
- }
-
- /**
- * Check if an event have been triggered
- *
- * @access public
- * @param string $eventName Event name
- * @return bool
- */
- public function isEventTriggered($eventName)
- {
- return in_array($eventName, $this->events);
- }
-
- /**
- * Flush the list of triggered events
- *
- * @access public
- */
- public function clearTriggeredEvents()
- {
- $this->events = array();
- $this->lastEvent = '';
- }
-
- /**
- * Check if a listener bind to an event
- *
- * @access public
- * @param string $eventName Event name
- * @param mixed $instance Instance name or object itself
- * @return bool Yes or no
- */
- public function hasListener($eventName, $instance)
- {
- if (isset($this->listeners[$eventName])) {
- foreach ($this->listeners[$eventName] as $listener) {
- if ($listener instanceof $instance) {
- return true;
- }
- }
- }
-
- return false;
- }
-}
diff --git a/app/Core/Helper.php b/app/Core/Helper.php
new file mode 100644
index 00000000..53084a7e
--- /dev/null
+++ b/app/Core/Helper.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Core;
+
+/**
+ * Helper base class
+ *
+ * @package core
+ * @author Frederic Guillot
+ *
+ * @property \Helper\App $app
+ * @property \Helper\Asset $asset
+ * @property \Helper\Datetime $datetime
+ * @property \Helper\File $file
+ * @property \Helper\Form $form
+ * @property \Helper\Subtask $subtask
+ * @property \Helper\Task $task
+ * @property \Helper\Text $text
+ * @property \Helper\Url $url
+ * @property \Helper\User $user
+ */
+class Helper extends Base
+{
+ /**
+ * Helper instances
+ *
+ * @static
+ * @access private
+ * @var array
+ */
+ private static $helpers = array();
+
+ /**
+ * Load automatically helpers
+ *
+ * @access public
+ * @param string $name Helper name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ if (! isset(self::$helpers[$name])) {
+ $class = '\Helper\\'.ucfirst($name);
+ self::$helpers[$name] = new $class($this->container);
+ }
+
+ return self::$helpers[$name];
+ }
+
+ /**
+ * HTML escaping
+ *
+ * @param string $value Value to escape
+ * @return string
+ */
+ public function e($value)
+ {
+ return htmlspecialchars($value, ENT_QUOTES, 'UTF-8', false);
+ }
+}
diff --git a/app/Core/HttpClient.php b/app/Core/HttpClient.php
new file mode 100644
index 00000000..2f280a1e
--- /dev/null
+++ b/app/Core/HttpClient.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace Core;
+
+/**
+ * HTTP client
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+class HttpClient extends Base
+{
+ /**
+ * HTTP connection timeout in seconds
+ *
+ * @var integer
+ */
+ const HTTP_TIMEOUT = 5;
+
+ /**
+ * Number of maximum redirections for the HTTP client
+ *
+ * @var integer
+ */
+ const HTTP_MAX_REDIRECTS = 2;
+
+ /**
+ * HTTP client user agent
+ *
+ * @var string
+ */
+ const HTTP_USER_AGENT = 'Kanboard';
+
+ /**
+ * Send a POST HTTP request encoded in JSON
+ *
+ * @access public
+ * @param string $url
+ * @param array $data
+ * @param array $headers
+ * @return string
+ */
+ public function postJson($url, array $data, array $headers = array())
+ {
+ return $this->doRequest(
+ $url,
+ json_encode($data),
+ array_merge(array('Content-type: application/json'), $headers)
+ );
+ }
+
+ /**
+ * Send a POST HTTP request encoded in www-form-urlencoded
+ *
+ * @access public
+ * @param string $url
+ * @param array $data
+ * @param array $headers
+ * @return string
+ */
+ public function postForm($url, array $data, array $headers = array())
+ {
+ return $this->doRequest(
+ $url,
+ http_build_query($data),
+ array_merge(array('Content-type: application/x-www-form-urlencoded'), $headers)
+ );
+ }
+
+ /**
+ * Make the HTTP request
+ *
+ * @access private
+ * @param string $url
+ * @param array $content
+ * @param array $headers
+ * @return string
+ */
+ private function doRequest($url, $content, array $headers)
+ {
+ if (empty($url)) {
+ return '';
+ }
+
+ $headers = array_merge(array('User-Agent: '.self::HTTP_USER_AGENT, 'Connection: close'), $headers);
+
+ $context = stream_context_create(array(
+ 'http' => array(
+ 'method' => 'POST',
+ 'protocol_version' => 1.1,
+ 'timeout' => self::HTTP_TIMEOUT,
+ 'max_redirects' => self::HTTP_MAX_REDIRECTS,
+ 'header' => implode("\r\n", $headers),
+ 'content' => $content
+ )
+ ));
+
+ $stream = @fopen(trim($url), 'r', false, $context);
+ $response = '';
+
+ if (is_resource($stream)) {
+ $response = stream_get_contents($stream);
+ }
+ else {
+ $this->container['logger']->error('HttpClient: request failed');
+ }
+
+ if (DEBUG) {
+ $this->container['logger']->debug('HttpClient: url='.$url);
+ $this->container['logger']->debug('HttpClient: payload='.$content);
+ $this->container['logger']->debug('HttpClient: metadata='.var_export(@stream_get_meta_data($stream), true));
+ $this->container['logger']->debug('HttpClient: response='.$response);
+ }
+
+ return $response;
+ }
+}
diff --git a/app/Core/Listener.php b/app/Core/Listener.php
deleted file mode 100644
index 9c96cd57..00000000
--- a/app/Core/Listener.php
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-namespace Core;
-
-/**
- * Event listener interface
- *
- * @package core
- * @author Frederic Guillot
- */
-interface Listener
-{
- /**
- * Execute the listener
- *
- * @access public
- * @param array $data Event data
- * @return boolean
- */
- public function execute(array $data);
-}
diff --git a/app/Core/Loader.php b/app/Core/Loader.php
deleted file mode 100644
index 151081c1..00000000
--- a/app/Core/Loader.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-
-namespace Core;
-
-/**
- * Loader class
- *
- * @package core
- * @author Frederic Guillot
- */
-class Loader
-{
- /**
- * List of paths
- *
- * @access private
- * @var array
- */
- private $paths = array();
-
- /**
- * Load the missing class
- *
- * @access public
- * @param string $class Class name with namespace
- */
- public function load($class)
- {
- foreach ($this->paths as $path) {
-
- $filename = $path.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $class).'.php';
-
- if (file_exists($filename)) {
- require $filename;
- break;
- }
- }
- }
-
- /**
- * Register the autoloader
- *
- * @access public
- */
- public function execute()
- {
- spl_autoload_register(array($this, 'load'));
- }
-
- /**
- * Register a new path
- *
- * @access public
- * @param string $path Path
- * @return Core\Loader
- */
- public function setPath($path)
- {
- $this->paths[] = $path;
- return $this;
- }
-}
diff --git a/app/Core/Markdown.php b/app/Core/Markdown.php
new file mode 100644
index 00000000..fa4e8080
--- /dev/null
+++ b/app/Core/Markdown.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Core;
+
+use Parsedown;
+use Helper\Url;
+
+/**
+ * Specific Markdown rules for Kanboard
+ *
+ * @package core
+ * @author norcnorc
+ * @author Frederic Guillot
+ */
+class Markdown extends Parsedown
+{
+ private $link;
+ private $helper;
+
+ public function __construct($link, Url $helper)
+ {
+ $this->link = $link;
+ $this->helper = $helper;
+ $this->InlineTypes['#'][] = 'TaskLink';
+ $this->inlineMarkerList .= '#';
+ }
+
+ protected function inlineTaskLink($Excerpt)
+ {
+ // Replace task #123 by a link to the task
+ if (! empty($this->link) && preg_match('!#(\d+)!i', $Excerpt['text'], $matches)) {
+
+ $url = $this->helper->href(
+ $this->link['controller'],
+ $this->link['action'],
+ $this->link['params'] + array('task_id' => $matches[1])
+ );
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $matches[0],
+ 'attributes' => array('href' => $url)));
+ }
+ }
+}
diff --git a/app/Core/MemoryCache.php b/app/Core/MemoryCache.php
new file mode 100644
index 00000000..f80a66ef
--- /dev/null
+++ b/app/Core/MemoryCache.php
@@ -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]);
+ }
+}
diff --git a/app/Core/Paginator.php b/app/Core/Paginator.php
new file mode 100644
index 00000000..12cc05a1
--- /dev/null
+++ b/app/Core/Paginator.php
@@ -0,0 +1,461 @@
+<?php
+
+namespace Core;
+
+use Pimple\Container;
+use PicoDb\Table;
+
+/**
+ * Paginator helper
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+class Paginator
+{
+ /**
+ * Container instance
+ *
+ * @access private
+ * @var \Pimple\Container
+ */
+ private $container;
+
+ /**
+ * Total number of items
+ *
+ * @access private
+ * @var integer
+ */
+ private $total = 0;
+
+ /**
+ * Page number
+ *
+ * @access private
+ * @var integer
+ */
+ private $page = 1;
+
+ /**
+ * Offset
+ *
+ * @access private
+ * @var integer
+ */
+ private $offset = 0;
+
+ /**
+ * Limit
+ *
+ * @access private
+ * @var integer
+ */
+ private $limit = 0;
+
+ /**
+ * Sort by this column
+ *
+ * @access private
+ * @var string
+ */
+ private $order = '';
+
+ /**
+ * Sorting direction
+ *
+ * @access private
+ * @var string
+ */
+ private $direction = 'ASC';
+
+ /**
+ * Slice of items
+ *
+ * @access private
+ * @var array
+ */
+ private $items = array();
+
+ /**
+ * PicoDb Table instance
+ *
+ * @access private
+ * @var \Picodb\Table
+ */
+ private $query = null;
+
+ /**
+ * Controller name
+ *
+ * @access private
+ * @var string
+ */
+ private $controller = '';
+
+ /**
+ * Action name
+ *
+ * @access private
+ * @var string
+ */
+ private $action = '';
+
+ /**
+ * Url params
+ *
+ * @access private
+ * @var array
+ */
+ private $params = array();
+
+ /**
+ * Constructor
+ *
+ * @access public
+ * @param \Pimple\Container $container
+ */
+ public function __construct(Container $container)
+ {
+ $this->container = $container;
+ }
+
+ /**
+ * Set a PicoDb query
+ *
+ * @access public
+ * @param \PicoDb\Table
+ * @return Paginator
+ */
+ public function setQuery(Table $query)
+ {
+ $this->query = $query;
+ $this->total = $this->query->count();
+ return $this;
+ }
+
+ /**
+ * Execute a PicoDb query
+ *
+ * @access public
+ * @return array
+ */
+ public function executeQuery()
+ {
+ if ($this->query !== null) {
+ return $this->query
+ ->offset($this->offset)
+ ->limit($this->limit)
+ ->orderBy($this->order, $this->direction)
+ ->findAll();
+ }
+
+ return array();
+ }
+
+ /**
+ * Set url parameters
+ *
+ * @access public
+ * @param string $controller
+ * @param string $action
+ * @param array $params
+ * @return Paginator
+ */
+ public function setUrl($controller, $action, array $params = array())
+ {
+ $this->controller = $controller;
+ $this->action = $action;
+ $this->params = $params;
+ return $this;
+ }
+
+ /**
+ * Add manually items
+ *
+ * @access public
+ * @param array $items
+ * @return Paginator
+ */
+ public function setCollection(array $items)
+ {
+ $this->items = $items;
+ return $this;
+ }
+
+ /**
+ * Return the items
+ *
+ * @access public
+ * @return array
+ */
+ public function getCollection()
+ {
+ return $this->items ?: $this->executeQuery();
+ }
+
+ /**
+ * Set the total number of items
+ *
+ * @access public
+ * @param integer $total
+ * @return Paginator
+ */
+ public function setTotal($total)
+ {
+ $this->total = $total;
+ return $this;
+ }
+
+ /**
+ * Get the total number of items
+ *
+ * @access public
+ * @return integer
+ */
+ public function getTotal()
+ {
+ return $this->total;
+ }
+
+ /**
+ * Set the default page number
+ *
+ * @access public
+ * @param integer $page
+ * @return Paginator
+ */
+ public function setPage($page)
+ {
+ $this->page = $page;
+ return $this;
+ }
+
+ /**
+ * Set the default column order
+ *
+ * @access public
+ * @param string $order
+ * @return Paginator
+ */
+ public function setOrder($order)
+ {
+ $this->order = $order;
+ return $this;
+ }
+
+ /**
+ * Set the default sorting direction
+ *
+ * @access public
+ * @param string $direction
+ * @return Paginator
+ */
+ public function setDirection($direction)
+ {
+ $this->direction = $direction;
+ return $this;
+ }
+
+ /**
+ * Set the maximum number of items per page
+ *
+ * @access public
+ * @param integer $limit
+ * @return Paginator
+ */
+ public function setMax($limit)
+ {
+ $this->limit = $limit;
+ return $this;
+ }
+
+ /**
+ * Return true if the collection is empty
+ *
+ * @access public
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ return $this->total === 0;
+ }
+
+ /**
+ * Execute the offset calculation only if the $condition is true
+ *
+ * @access public
+ * @param boolean $condition
+ * @return Paginator
+ */
+ public function calculateOnlyIf($condition)
+ {
+ if ($condition) {
+ $this->calculate();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Calculate the offset value accoring to url params and the page number
+ *
+ * @access public
+ * @return Paginator
+ */
+ public function calculate()
+ {
+ $this->page = $this->container['request']->getIntegerParam('page', 1);
+ $this->direction = $this->container['request']->getStringParam('direction', $this->direction);
+ $this->order = $this->container['request']->getStringParam('order', $this->order);
+
+ if ($this->page < 1) {
+ $this->page = 1;
+ }
+
+ $this->offset = ($this->page - 1) * $this->limit;
+
+ return $this;
+ }
+
+ /**
+ * Get url params for link generation
+ *
+ * @access public
+ * @param integer $page
+ * @param string $order
+ * @param string $direction
+ * @return string
+ */
+ public function getUrlParams($page, $order, $direction)
+ {
+ $params = array(
+ 'page' => $page,
+ 'order' => $order,
+ 'direction' => $direction,
+ );
+
+ return array_merge($this->params, $params);
+ }
+
+ /**
+ * Generate the previous link
+ *
+ * @access public
+ * @return string
+ */
+ public function generatePreviousLink()
+ {
+ $html = '<span class="pagination-previous">';
+
+ if ($this->offset > 0) {
+ $html .= $this->container['helper']->url->link(
+ '&larr; '.t('Previous'),
+ $this->controller,
+ $this->action,
+ $this->getUrlParams($this->page - 1, $this->order, $this->direction)
+ );
+ }
+ else {
+ $html .= '&larr; '.t('Previous');
+ }
+
+ $html .= '</span>';
+
+ return $html;
+ }
+
+ /**
+ * Generate the next link
+ *
+ * @access public
+ * @return string
+ */
+ public function generateNextLink()
+ {
+ $html = '<span class="pagination-next">';
+
+ if (($this->total - $this->offset) > $this->limit) {
+ $html .= $this->container['helper']->url->link(
+ t('Next').' &rarr;',
+ $this->controller,
+ $this->action,
+ $this->getUrlParams($this->page + 1, $this->order, $this->direction)
+ );
+ }
+ else {
+ $html .= t('Next').' &rarr;';
+ }
+
+ $html .= '</span>';
+
+ return $html;
+ }
+
+ /**
+ * Return true if there is no pagination to show
+ *
+ * @access public
+ * @return boolean
+ */
+ public function hasNothingtoShow()
+ {
+ return $this->offset === 0 && ($this->total - $this->offset) <= $this->limit;
+ }
+
+ /**
+ * Generation pagination links
+ *
+ * @access public
+ * @return string
+ */
+ public function toHtml()
+ {
+ $html = '';
+
+ if (! $this->hasNothingtoShow()) {
+ $html .= '<div class="pagination">';
+ $html .= $this->generatePreviousLink();
+ $html .= $this->generateNextLink();
+ $html .= '</div>';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Magic method to output pagination links
+ *
+ * @access public
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toHtml();
+ }
+
+ /**
+ * Column sorting
+ *
+ * @param string $label Column title
+ * @param string $column SQL column name
+ * @return string
+ */
+ public function order($label, $column)
+ {
+ $prefix = '';
+ $direction = 'ASC';
+
+ if ($this->order === $column) {
+ $prefix = $this->direction === 'DESC' ? '&#9660; ' : '&#9650; ';
+ $direction = $this->direction === 'DESC' ? 'ASC' : 'DESC';
+ }
+
+ return $prefix.$this->container['helper']->url->link(
+ $label,
+ $this->controller,
+ $this->action,
+ $this->getUrlParams($this->page, $column, $direction)
+ );
+ }
+}
diff --git a/app/Core/Registry.php b/app/Core/Registry.php
deleted file mode 100644
index d8b9063e..00000000
--- a/app/Core/Registry.php
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-
-namespace Core;
-
-use RuntimeException;
-
-/**
- * The registry class is a dependency injection container
- *
- * @property mixed db
- * @property mixed event
- * @package core
- * @author Frederic Guillot
- */
-class Registry
-{
- /**
- * Contains all dependencies
- *
- * @access private
- * @var array
- */
- private $container = array();
-
- /**
- * Contains all instances
- *
- * @access private
- * @var array
- */
- private $instances = array();
-
- /**
- * Set a dependency
- *
- * @access public
- * @param string $name Unique identifier for the service/parameter
- * @param mixed $value The value of the parameter or a closure to define an object
- */
- public function __set($name, $value)
- {
- $this->container[$name] = $value;
- }
-
- /**
- * Get a dependency
- *
- * @access public
- * @param string $name Unique identifier for the service/parameter
- * @return mixed The value of the parameter or an object
- * @throws RuntimeException If the identifier is not found
- */
- public function __get($name)
- {
- if (isset($this->container[$name])) {
-
- if (is_callable($this->container[$name])) {
- return $this->container[$name]();
- }
- else {
- return $this->container[$name];
- }
- }
-
- throw new \RuntimeException('Identifier not found in the registry: '.$name);
- }
-
- /**
- * Return a shared instance of a dependency
- *
- * @access public
- * @param string $name Unique identifier for the service/parameter
- * @return mixed Same object instance of the dependency
- */
- public function shared($name)
- {
- if (! isset($this->instances[$name])) {
- $this->instances[$name] = $this->$name;
- }
-
- return $this->instances[$name];
- }
-}
diff --git a/app/Core/Request.php b/app/Core/Request.php
index a4c426f0..c7ca3184 100644
--- a/app/Core/Request.php
+++ b/app/Core/Request.php
@@ -76,6 +76,17 @@ class Request
}
/**
+ * Get the Json request body
+ *
+ * @access public
+ * @return array
+ */
+ public function getJson()
+ {
+ return json_decode($this->getBody(), true);
+ }
+
+ /**
* Get the content of an uploaded file
*
* @access public
@@ -114,6 +125,20 @@ class Request
}
/**
+ * Check if the page is requested through HTTPS
+ *
+ * Note: IIS return the value 'off' and other web servers an empty value when it's not HTTPS
+ *
+ * @static
+ * @access public
+ * @return boolean
+ */
+ public static function isHTTPS()
+ {
+ return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== '' && $_SERVER['HTTPS'] !== 'off';
+ }
+
+ /**
* Return a HTTP header value
*
* @access public
diff --git a/app/Core/Response.php b/app/Core/Response.php
index 347cdde7..d42a8f1e 100644
--- a/app/Core/Response.php
+++ b/app/Core/Response.php
@@ -168,6 +168,23 @@ class Response
}
/**
+ * Send a css response
+ *
+ * @access public
+ * @param string $data Raw data
+ * @param integer $status_code HTTP status code
+ */
+ public function css($data, $status_code = 200)
+ {
+ $this->status($status_code);
+
+ header('Content-Type: text/css; charset=utf-8');
+ echo $data;
+
+ exit;
+ }
+
+ /**
* Send a binary response
*
* @access public
@@ -195,24 +212,7 @@ class Response
$policies['default-src'] = "'self'";
$values = '';
- foreach ($policies as $policy => $hosts) {
-
- if (is_array($hosts)) {
-
- $acl = '';
-
- foreach ($hosts as &$host) {
-
- if ($host === '*' || $host === 'self' || strpos($host, 'http') === 0) {
- $acl .= $host.' ';
- }
- }
- }
- else {
-
- $acl = $hosts;
- }
-
+ foreach ($policies as $policy => $acl) {
$values .= $policy.' '.trim($acl).'; ';
}
@@ -246,7 +246,7 @@ class Response
*/
public function hsts()
{
- if (Tool::isHTTPS()) {
+ if (Request::isHTTPS()) {
header('Strict-Transport-Security: max-age=31536000');
}
}
diff --git a/app/Core/Router.php b/app/Core/Router.php
index c9af6e2c..36c11a0a 100644
--- a/app/Core/Router.php
+++ b/app/Core/Router.php
@@ -2,6 +2,8 @@
namespace Core;
+use Pimple\Container;
+
/**
* Router class
*
@@ -27,24 +29,24 @@ class Router
private $action = '';
/**
- * Registry instance
+ * Container instance
*
* @access private
- * @var \Core\Registry
+ * @var \Pimple\Container
*/
- private $registry;
+ private $container;
/**
* Constructor
*
* @access public
- * @param Registry $registry Registry instance
- * @param string $controller Controller name
- * @param string $action Action name
+ * @param \Pimple\Container $container Container instance
+ * @param string $controller Controller name
+ * @param string $action Action name
*/
- public function __construct(Registry $registry, $controller = '', $action = '')
+ public function __construct(Container $container, $controller = '', $action = '')
{
- $this->registry = $registry;
+ $this->container = $container;
$this->controller = empty($_GET['controller']) ? $controller : $_GET['controller'];
$this->action = empty($_GET['action']) ? $action : $_GET['action'];
}
@@ -81,11 +83,7 @@ class Router
return false;
}
- $instance = new $class($this->registry);
- $instance->request = new Request;
- $instance->response = new Response;
- $instance->session = new Session;
- $instance->template = new Template;
+ $instance = new $class($this->container);
$instance->beforeAction($this->controller, $this->action);
$instance->$method();
diff --git a/app/Core/Session.php b/app/Core/Session.php
index 6028f0b9..c35014cd 100644
--- a/app/Core/Session.php
+++ b/app/Core/Session.php
@@ -2,13 +2,15 @@
namespace Core;
+use ArrayAccess;
+
/**
* Session class
*
* @package core
* @author Frederic Guillot
*/
-class Session
+class Session implements ArrayAccess
{
/**
* Sesion lifetime
@@ -36,32 +38,32 @@ class Session
*
* @access public
* @param string $base_path Cookie path
- * @param string $save_path Custom session save path
*/
- public function open($base_path = '/', $save_path = '')
+ public function open($base_path = '/')
{
- if ($save_path !== '') {
- session_save_path($save_path);
- }
-
+ $base_path = str_replace('\\', '/', $base_path);
+
// HttpOnly and secure flags for session cookie
session_set_cookie_params(
self::SESSION_LIFETIME,
$base_path ?: '/',
null,
- Tool::isHTTPS(),
+ Request::isHTTPS(),
true
);
// Avoid session id in the URL
ini_set('session.use_only_cookies', '1');
+ // Enable strict mode
+ ini_set('session.use_strict_mode', '1');
+
// Ensure session ID integrity
ini_set('session.entropy_file', '/dev/urandom');
ini_set('session.entropy_length', '32');
ini_set('session.hash_bits_per_character', 6);
- // If session was autostarted with session.auto_start = 1 in php.ini destroy it, otherwise we cannot login
+ // If the session was autostarted with session.auto_start = 1 in php.ini destroy it
if (isset($_SESSION)) {
session_destroy();
}
@@ -90,19 +92,17 @@ class Session
$_SESSION = array();
// Destroy the session cookie
- if (ini_get('session.use_cookies')) {
- $params = session_get_cookie_params();
-
- setcookie(
- session_name(),
- '',
- time() - 42000,
- $params['path'],
- $params['domain'],
- $params['secure'],
- $params['httponly']
- );
- }
+ $params = session_get_cookie_params();
+
+ setcookie(
+ session_name(),
+ '',
+ time() - 42000,
+ $params['path'],
+ $params['domain'],
+ $params['secure'],
+ $params['httponly']
+ );
// Destroy session data
session_destroy();
@@ -129,4 +129,24 @@ class Session
{
$_SESSION['flash_error_message'] = $message;
}
+
+ public function offsetSet($offset, $value)
+ {
+ $_SESSION[$offset] = $value;
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($_SESSION[$offset]);
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($_SESSION[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
+ }
}
diff --git a/app/Core/Template.php b/app/Core/Template.php
index f21e8a6d..9688c2a5 100644
--- a/app/Core/Template.php
+++ b/app/Core/Template.php
@@ -10,28 +10,28 @@ use LogicException;
* @package core
* @author Frederic Guillot
*/
-class Template
+class Template extends Helper
{
/**
* Template path
*
* @var string
*/
- const PATH = 'app/Templates/';
+ const PATH = 'app/Template/';
/**
- * Load a template
+ * Render a template
*
* Example:
*
- * $template->load('template_name', ['bla' => 'value']);
+ * $template->render('template_name', ['bla' => 'value']);
*
* @access public
* @params string $__template_name Template name
* @params array $__template_args Key/Value map of template variables
* @return string
*/
- public function load($__template_name, array $__template_args = array())
+ public function render($__template_name, array $__template_args = array())
{
$__template_file = self::PATH.$__template_name.'.php';
@@ -57,9 +57,9 @@ class Template
*/
public function layout($template_name, array $template_args = array(), $layout_name = 'layout')
{
- return $this->load(
+ return $this->render(
$layout_name,
- $template_args + array('content_for_layout' => $this->load($template_name, $template_args))
+ $template_args + array('content_for_layout' => $this->render($template_name, $template_args))
);
}
}
diff --git a/app/Core/Tool.php b/app/Core/Tool.php
index e54a0d3b..84e42ba8 100644
--- a/app/Core/Tool.php
+++ b/app/Core/Tool.php
@@ -33,35 +33,22 @@ class Tool
}
/**
- * Load and register a model
+ * Get the mailbox hash from an email address
*
* @static
* @access public
- * @param Core\Registry $registry DPI container
- * @param string $name Model name
- * @return mixed
+ * @param string $email
+ * @return string
*/
- public static function loadModel(Registry $registry, $name)
+ public static function getMailboxHash($email)
{
- if (! isset($registry->$name)) {
- $class = '\Model\\'.ucfirst($name);
- $registry->$name = new $class($registry);
+ if (! strpos($email, '@') || ! strpos($email, '+')) {
+ return '';
}
- return $registry->shared($name);
- }
+ list($local_part,) = explode('@', $email);
+ list(,$identifier) = explode('+', $local_part);
- /**
- * Check if the page is requested through HTTPS
- *
- * Note: IIS return the value 'off' and other web servers an empty value when it's not HTTPS
- *
- * @static
- * @access public
- * @return boolean
- */
- public static function isHTTPS()
- {
- return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== '' && $_SERVER['HTTPS'] !== 'off';
+ return $identifier;
}
}
diff --git a/app/Core/Translator.php b/app/Core/Translator.php
index 43e934a9..e3d19692 100644
--- a/app/Core/Translator.php
+++ b/app/Core/Translator.php
@@ -11,14 +11,14 @@ namespace Core;
class Translator
{
/**
- * Locales path
+ * Locale path
*
* @var string
*/
- const PATH = 'app/Locales/';
+ const PATH = 'app/Locale/';
/**
- * Locales
+ * Locale
*
* @static
* @access private
@@ -27,6 +27,31 @@ class Translator
private static $locales = array();
/**
+ * Instance
+ *
+ * @static
+ * @access private
+ * @var Translator
+ */
+ private static $instance = null;
+
+ /**
+ * Get instance
+ *
+ * @static
+ * @access public
+ * @return Translator
+ */
+ public static function getInstance()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self;
+ }
+
+ return self::$instance;
+ }
+
+ /**
* Get a translation
*
* $translator->translate('I have %d kids', 5);
@@ -181,5 +206,8 @@ class Translator
if (file_exists($filename)) {
self::$locales = require $filename;
}
+ else {
+ self::$locales = array();
+ }
}
}