Commit c3e5f3ae by Scott

Merge branch 'controllers' into dev

parents 990a0f4a a0c6a196
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
More about this license: http://www.question2answer.org/license.php More about this license: http://www.question2answer.org/license.php
*/ */
use Q2A\Exceptions\ExceptionHandler;
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../../'); header('Location: ../../');
exit; exit;
...@@ -178,7 +180,20 @@ function qa_get_request_content() ...@@ -178,7 +180,20 @@ function qa_get_request_content()
$firstlower = strtolower($requestparts[0]); $firstlower = strtolower($requestparts[0]);
$routing = qa_page_routing(); $routing = qa_page_routing();
if (isset($routing[$requestlower])) { qa_routing_config();
$route = qa_service('router')->match($requestlower);
if ($route !== null) {
// use new Controller system
qa_set_template($route->getId());
$controllerClass = $route->getController();
$ctrl = new $controllerClass();
try {
$qa_content = $ctrl->executeAction($route->getAction(), $route->getParameters());
} catch (Exception $e) {
$qa_content = (new ExceptionHandler())->handle($e);
}
} elseif (isset($routing[$requestlower])) {
qa_set_template($firstlower); qa_set_template($firstlower);
$qa_content = require QA_INCLUDE_DIR . $routing[$requestlower]; $qa_content = require QA_INCLUDE_DIR . $routing[$requestlower];
...@@ -437,16 +452,32 @@ function qa_page_routing() ...@@ -437,16 +452,32 @@ function qa_page_routing()
'unanswered/' => 'pages/unanswered.php', 'unanswered/' => 'pages/unanswered.php',
'unsubscribe' => 'pages/unsubscribe.php', 'unsubscribe' => 'pages/unsubscribe.php',
'updates' => 'pages/updates.php', 'updates' => 'pages/updates.php',
'user/' => 'pages/user.php',
'users' => 'pages/users.php',
'users/blocked' => 'pages/users-blocked.php',
'users/new' => 'pages/users-newest.php',
'users/special' => 'pages/users-special.php',
); );
} }
/** /**
* Set up routing.
*/
function qa_routing_config()
{
$router = qa_service('router');
$router->addRoute('user', 'get', 'user/{str}', '\Q2A\Controllers\User\UserProfile', 'profile');
$router->addRoute('user', 'post', 'user/{str}', '\Q2A\Controllers\User\UserProfile', 'profile');
$router->addRoute('user-self', 'get', 'user', '\Q2A\Controllers\User\UserProfile', 'index');
$router->addRoute('user-wall', 'get', 'user/{str}/wall', '\Q2A\Controllers\User\UserMessages', 'wall');
$router->addRoute('user-activity', 'get', 'user/{str}/activity', '\Q2A\Controllers\User\UserPosts', 'activity');
$router->addRoute('user-questions', 'get', 'user/{str}/questions', '\Q2A\Controllers\User\UserPosts', 'questions');
$router->addRoute('user-answers', 'get', 'user/{str}/answers', '\Q2A\Controllers\User\UserPosts', 'answers');
$router->addRoute('user-top', 'get', 'users', '\Q2A\Controllers\User\UsersList', 'top');
$router->addRoute('user-blocked', 'get', 'users/blocked', '\Q2A\Controllers\User\UsersList', 'blocked');
$router->addRoute('user-new', 'get', 'users/new', '\Q2A\Controllers\User\UsersList', 'newest');
$router->addRoute('user-special', 'get', 'users/special', '\Q2A\Controllers\User\UsersList', 'special');
}
/**
* Sets the template which should be passed to the theme class, telling it which type of page it's displaying * Sets the template which should be passed to the theme class, telling it which type of page it's displaying
* @param $template * @param $template
*/ */
......
...@@ -1570,7 +1570,7 @@ function qa_db_newest_users_selectspec($start, $count = null) ...@@ -1570,7 +1570,7 @@ function qa_db_newest_users_selectspec($start, $count = null)
function qa_db_users_from_level_selectspec($level) function qa_db_users_from_level_selectspec($level)
{ {
return array( return array(
'columns' => array('^users.userid', 'handle', 'level'), 'columns' => array('^users.userid', 'handle', 'flags', 'level', 'email', 'avatarblobid' => 'BINARY avatarblobid', 'avatarwidth', 'avatarheight'),
'source' => '^users WHERE level>=# ORDER BY level DESC', 'source' => '^users WHERE level>=# ORDER BY level DESC',
'arguments' => array($level), 'arguments' => array($level),
'sortdesc' => 'level', 'sortdesc' => 'level',
...@@ -1597,7 +1597,7 @@ function qa_db_users_with_flag_selectspec($flag, $start = 0, $limit = null) ...@@ -1597,7 +1597,7 @@ function qa_db_users_with_flag_selectspec($flag, $start = 0, $limit = null)
} }
return array( return array(
'columns' => array('^users.userid', 'handle', 'flags', 'level'), 'columns' => array('^users.userid', 'handle', 'flags', 'level', 'email', 'avatarblobid' => 'BINARY avatarblobid', 'avatarwidth', 'avatarheight'),
'source' => $source, 'source' => $source,
'arguments' => $arguments, 'arguments' => $arguments,
); );
......
...@@ -41,9 +41,14 @@ function qa_autoload($class) ...@@ -41,9 +41,14 @@ function qa_autoload($class)
{ {
if (strpos($class, 'Q2A_') === 0) if (strpos($class, 'Q2A_') === 0)
require QA_INCLUDE_DIR . strtr($class, '_', '/') . '.php'; require QA_INCLUDE_DIR . strtr($class, '_', '/') . '.php';
if (strpos($class, 'Q2A\\') === 0) {
require QA_BASE_DIR . 'qa-src/' . strtr(substr($class, 4), '\\', '/') . '.php';
}
} }
spl_autoload_register('qa_autoload'); spl_autoload_register('qa_autoload');
use Q2A\App\Application;
// Execution section of this file - remainder contains function definitions // Execution section of this file - remainder contains function definitions
...@@ -58,7 +63,6 @@ if (defined('QA_WORDPRESS_LOAD_FILE')) { ...@@ -58,7 +63,6 @@ if (defined('QA_WORDPRESS_LOAD_FILE')) {
require_once QA_JOOMLA_LOAD_FILE; require_once QA_JOOMLA_LOAD_FILE;
} }
qa_initialize_constants_2(); qa_initialize_constants_2();
qa_initialize_modularity(); qa_initialize_modularity();
qa_register_core_modules(); qa_register_core_modules();
...@@ -1839,6 +1843,36 @@ function qa_retrieve_url($url) ...@@ -1839,6 +1843,36 @@ function qa_retrieve_url($url)
/** /**
* Helper function to access the Application object.
* @return Application
*/
function qa_app()
{
return Application::getInstance();
}
/**
* Helper function to access services in the Container.
* If the $key parameter is set and the $object parameter is null the container is called to resolve the $key.
* If the $key and the $object parameters are null the container is called to bind the $object to the $key.
* @param mixed $key Identifier for the object to get/set.
* @param mixed $object Object to set in the $key (if null, a stored object is returned)
* @return mixed
*/
function qa_service($key, $object = null)
{
$app = Application::getInstance();
if ($object === null) {
return $app->getContainer()->get($key);
}
$app->getContainer()->set($key, $object);
}
/**
* Shortcut to get or set an option value without specifying database * Shortcut to get or set an option value without specifying database
* @param $name * @param $name
* @param mixed $value * @param mixed $value
......
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\App;
use Q2A\Http\Router;
class Application
{
/** @var Container */
private $container;
/** @var static */
protected static $instance;
protected function __construct()
{
$this->container = new Container();
$this->registerCoreServices();
}
/**
* Instantiate and fetch the application as a singleton.
* @return static
*/
public static function getInstance()
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
/**
* Register the services used by the core.
*/
private function registerCoreServices()
{
$this->container->set('router', new Router());
}
/**
* Return the container instance.
* @return Container
*/
public function getContainer()
{
return $this->container;
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\App;
use Q2A\Exceptions\FatalErrorException;
class Container
{
/** @var array */
protected $services = array();
/**
* Bind an object to a key.
* @param string $key The key to bind the object to
* @param mixed $object The object to bind to the key
*/
public function set($key, $object)
{
$this->services[$key] = $object;
}
/**
* Return an object assigned to the given key. If the key is not found an exception is thrown.
* @param string $key The key to look for
* @return mixed
*/
public function get($key)
{
if (isset($this->services[$key])) {
return $this->services[$key];
}
throw new FatalErrorException(sprintf('Key "%s" not found in container', $key));
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Auth;
use Q2A\Exceptions\FatalErrorException;
class InternalUsersOnlyException extends FatalErrorException
{
/**
* InternalUsersOnlyException constructor.
*
* @param string $message
*/
public function __construct($message = 'Feature only supported by internal users.')
{
parent::__construct($message);
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Auth;
use Q2A\Exceptions\ErrorMessageException;
class NoPermissionException extends ErrorMessageException
{
/**
* NoPermissionException constructor.
*
* @param string $message
*/
public function __construct($message = null)
{
if ($message === null) {
$message = qa_lang_html('users/no_permission');
}
parent::__construct($message);
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Controllers;
use Q2A\Middleware\BaseMiddleware;
abstract class BaseController
{
/** @var BaseMiddleware[string] */
private $middleware;
public function __construct()
{
// TODO: constructor taking Database class parameter
$this->middleware = array();
}
/**
* Attach a middleware to one action or an array of actions. Use '*' to match all actions.
*
* @param BaseMiddleware $middleware
* @param array|string $actions
*/
public function addMiddleware(BaseMiddleware $middleware, $actions = '*')
{
if (is_array($actions)) {
foreach ($actions as $action) {
$this->addMiddlewareToAction($middleware, $action);
}
} else { // If it is a string
$this->addMiddlewareToAction($middleware, $actions);
}
}
/**
* @param BaseMiddleware $middleware
* @param string $action
*/
private function addMiddlewareToAction(BaseMiddleware $middleware, $action)
{
if (!isset($this->middleware[$action])) {
$this->middleware[$action] = array();
}
$this->middleware[$action][] = $middleware;
}
/**
* Execute the given action with the given parameters on this controller. after running all
* middleware for the action. This method is expected to return a qa_content array or throw an
* exception.
*
* @param string $action Action to execute
* @param array $parameters Parameters to send to the action
*
* @return mixed
*/
public function executeAction($action, $parameters)
{
$this->executeMiddlewareForAction('*');
$this->executeMiddlewareForAction($action);
return call_user_func_array(array($this, $action), $parameters);
}
/**
* @param string $action
*/
private function executeMiddlewareForAction($action)
{
if (!isset($this->middleware[$action])) {
return;
}
foreach ($this->middleware[$action] as $middleware) {
$middleware->handle();
}
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Controllers\User;
use Q2A\Http\Exceptions\PageNotFoundException;
use Q2A\Middleware\Auth\InternalUsersOnly;
require_once QA_INCLUDE_DIR . 'db/selects.php';
require_once QA_INCLUDE_DIR . 'app/messages.php';
class UserMessages extends \Q2A\Controllers\BaseController
{
public function __construct()
{
parent::__construct();
$this->addMiddleware(new InternalUsersOnly());
}
/**
* @param string $handle
*
* @return array
* @throws PageNotFoundException
*/
public function wall($handle)
{
$userhtml = qa_html($handle);
$start = qa_get_start();
// Find the questions for this user
list($useraccount, $usermessages) = qa_db_select_with_pending(
qa_db_user_account_selectspec($handle, false),
qa_db_recent_messages_selectspec(null, null, $handle, false, qa_opt_if_loaded('page_size_wall'), $start)
);
if (!is_array($useraccount)) { // check the user exists
throw new PageNotFoundException();
}
// Perform pagination
$pagesize = qa_opt('page_size_wall');
$count = $useraccount['wallposts'];
$loginuserid = qa_get_logged_in_userid();
$usermessages = array_slice($usermessages, 0, $pagesize);
$usermessages = qa_wall_posts_add_rules($usermessages, $start);
// Process deleting or adding a wall post (similar but not identical code to qq-page-user-profile.php)
$errors = array();
$wallposterrorhtml = qa_wall_error_html($loginuserid, $useraccount['userid'], $useraccount['flags']);
foreach ($usermessages as $message) {
if ($message['deleteable'] && qa_clicked('m' . $message['messageid'] . '_dodelete')) {
if (!qa_check_form_security_code('wall-' . $useraccount['handle'], qa_post_text('code'))) {
$errors['page'] = qa_lang_html('misc/form_security_again');
} else {
qa_wall_delete_post($loginuserid, qa_get_logged_in_handle(), qa_cookie_get(), $message);
qa_redirect(qa_request(), $_GET);
}
}
}
if (qa_clicked('dowallpost')) {
$inmessage = qa_post_text('message');
if (!strlen($inmessage)) {
$errors['message'] = qa_lang('profile/post_wall_empty');
} elseif (!qa_check_form_security_code('wall-' . $useraccount['handle'], qa_post_text('code'))) {
$errors['message'] = qa_lang_html('misc/form_security_again');
} elseif (!$wallposterrorhtml) {
qa_wall_add_post($loginuserid, qa_get_logged_in_handle(), qa_cookie_get(), $useraccount['userid'], $useraccount['handle'], $inmessage, '');
qa_redirect(qa_request());
}
}
// Prepare content for theme
$qa_content = qa_content_prepare();
$qa_content['title'] = qa_lang_html_sub('profile/wall_for_x', $userhtml);
$qa_content['error'] = @$errors['page'];
$qa_content['message_list'] = array(
'tags' => 'id="wallmessages"',
'form' => array(
'tags' => 'name="wallpost" method="post" action="' . qa_self_html() . '"',
'style' => 'tall',
'hidden' => array(
'qa_click' => '', // for simulating clicks in Javascript
'handle' => qa_html($useraccount['handle']),
'start' => qa_html($start),
'code' => qa_get_form_security_code('wall-' . $useraccount['handle']),
),
),
'messages' => array(),
);
if ($start == 0) { // only allow posting on first page
if ($wallposterrorhtml) {
$qa_content['message_list']['error'] = $wallposterrorhtml; // an error that means we are not allowed to post
} else {
$qa_content['message_list']['form']['fields'] = array(
'message' => array(
'tags' => 'name="message" id="message"',
'value' => qa_html(@$inmessage, false),
'rows' => 2,
'error' => qa_html(@$errors['message']),
),
);
$qa_content['message_list']['form']['buttons'] = array(
'post' => array(
'tags' => 'name="dowallpost" onclick="return qa_submit_wall_post(this, false);"',
'label' => qa_lang_html('profile/post_wall_button'),
),
);
}
}
foreach ($usermessages as $message) {
$qa_content['message_list']['messages'][] = qa_wall_post_view($message);
}
$qa_content['page_links'] = qa_html_page_links(qa_request(), $start, $pagesize, $count, qa_opt('pages_prev_next'));
// Sub menu for navigation in user pages
$ismyuser = isset($loginuserid) && $loginuserid == $useraccount['userid'];
$qa_content['navigation']['sub'] = qa_user_sub_navigation($handle, 'wall', $ismyuser);
return $qa_content;
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Controllers\User;
use Q2A\Http\Exceptions\PageNotFoundException;
require_once QA_INCLUDE_DIR . 'db/users.php';
require_once QA_INCLUDE_DIR . 'db/selects.php';
require_once QA_INCLUDE_DIR . 'app/users.php';
require_once QA_INCLUDE_DIR . 'app/format.php';
class UserPosts extends \Q2A\Controllers\BaseController
{
protected $userid;
protected $userhtml;
/**
* @param string $handle
*
* @return array
* @throws PageNotFoundException
*/
public function activity($handle)
{
$this->userHtml($handle);
// Find the recent activity for this user
$loginuserid = qa_get_logged_in_userid();
$identifier = QA_FINAL_EXTERNAL_USERS ? $this->userid : $handle;
list($useraccount, $questions, $answerqs, $commentqs, $editqs) = qa_db_select_with_pending(
QA_FINAL_EXTERNAL_USERS ? null : qa_db_user_account_selectspec($handle, false),
qa_db_user_recent_qs_selectspec($loginuserid, $identifier, qa_opt_if_loaded('page_size_activity')),
qa_db_user_recent_a_qs_selectspec($loginuserid, $identifier),
qa_db_user_recent_c_qs_selectspec($loginuserid, $identifier),
qa_db_user_recent_edit_qs_selectspec($loginuserid, $identifier)
);
if (!QA_FINAL_EXTERNAL_USERS && !is_array($useraccount)) { // check the user exists
throw new PageNotFoundException();
}
// Get information on user references
$questions = qa_any_sort_and_dedupe(array_merge($questions, $answerqs, $commentqs, $editqs));
$questions = array_slice($questions, 0, qa_opt('page_size_activity'));
$usershtml = qa_userids_handles_html(qa_any_get_userids_handles($questions), false);
// Prepare content for theme
$qa_content = qa_content_prepare(true);
if (count($questions))
$qa_content['title'] = qa_lang_html_sub('profile/recent_activity_by_x', $this->userhtml);
else
$qa_content['title'] = qa_lang_html_sub('profile/no_posts_by_x', $this->userhtml);
// Recent activity by this user
$qa_content['q_list']['form'] = array(
'tags' => 'method="post" action="' . qa_self_html() . '"',
'hidden' => array(
'code' => qa_get_form_security_code('vote'),
),
);
$qa_content['q_list']['qs'] = array();
$htmldefaults = qa_post_html_defaults('Q');
$htmldefaults['whoview'] = false;
$htmldefaults['voteview'] = false;
$htmldefaults['avatarsize'] = 0;
foreach ($questions as $question) {
$qa_content['q_list']['qs'][] = qa_any_to_q_html_fields($question, $loginuserid, qa_cookie_get(),
$usershtml, null, array('voteview' => false) + qa_post_html_options($question, $htmldefaults));
}
// Sub menu for navigation in user pages
$ismyuser = isset($loginuserid) && $loginuserid == (QA_FINAL_EXTERNAL_USERS ? $this->userid : $useraccount['userid']);
$qa_content['navigation']['sub'] = qa_user_sub_navigation($handle, 'activity', $ismyuser);
return $qa_content;
}
/**
* @param string $handle
*
* @return array
* @throws PageNotFoundException
*/
public function questions($handle)
{
$this->userHtml($handle);
$start = qa_get_start();
// Find the questions for this user
$loginuserid = qa_get_logged_in_userid();
$identifier = QA_FINAL_EXTERNAL_USERS ? $this->userid : $handle;
list($useraccount, $userpoints, $questions) = qa_db_select_with_pending(
QA_FINAL_EXTERNAL_USERS ? null : qa_db_user_account_selectspec($handle, false),
qa_db_user_points_selectspec($identifier),
qa_db_user_recent_qs_selectspec($loginuserid, $identifier, qa_opt_if_loaded('page_size_qs'), $start)
);
if (!QA_FINAL_EXTERNAL_USERS && !is_array($useraccount)) { // check the user exists
throw new PageNotFoundException();
}
// Get information on user questions
$pagesize = qa_opt('page_size_qs');
$count = (int)@$userpoints['qposts'];
$questions = array_slice($questions, 0, $pagesize);
$usershtml = qa_userids_handles_html($questions, false);
// Prepare content for theme
$qa_content = qa_content_prepare(true);
if (count($questions))
$qa_content['title'] = qa_lang_html_sub('profile/questions_by_x', $this->userhtml);
else
$qa_content['title'] = qa_lang_html_sub('profile/no_questions_by_x', $this->userhtml);
// Recent questions by this user
$qa_content['q_list']['form'] = array(
'tags' => 'method="post" action="' . qa_self_html() . '"',
'hidden' => array(
'code' => qa_get_form_security_code('vote'),
),
);
$qa_content['q_list']['qs'] = array();
$htmldefaults = qa_post_html_defaults('Q');
$htmldefaults['whoview'] = false;
$htmldefaults['avatarsize'] = 0;
foreach ($questions as $question) {
$qa_content['q_list']['qs'][] = qa_post_html_fields($question, $loginuserid, qa_cookie_get(),
$usershtml, null, qa_post_html_options($question, $htmldefaults));
}
$qa_content['page_links'] = qa_html_page_links(qa_request(), $start, $pagesize, $count, qa_opt('pages_prev_next'));
// Sub menu for navigation in user pages
$ismyuser = isset($loginuserid) && $loginuserid == (QA_FINAL_EXTERNAL_USERS ? $this->userid : $useraccount['userid']);
$qa_content['navigation']['sub'] = qa_user_sub_navigation($handle, 'questions', $ismyuser);
return $qa_content;
}
/**
* @param string $handle
*
* @return array
* @throws PageNotFoundException
*/
public function answers($handle)
{
$this->userHtml($handle);
$start = qa_get_start();
// Find the questions for this user
$loginuserid = qa_get_logged_in_userid();
$identifier = QA_FINAL_EXTERNAL_USERS ? $this->userid : $handle;
list($useraccount, $userpoints, $questions) = qa_db_select_with_pending(
QA_FINAL_EXTERNAL_USERS ? null : qa_db_user_account_selectspec($handle, false),
qa_db_user_points_selectspec($identifier),
qa_db_user_recent_a_qs_selectspec($loginuserid, $identifier, qa_opt_if_loaded('page_size_activity'), $start)
);
if (!QA_FINAL_EXTERNAL_USERS && !is_array($useraccount)) { // check the user exists
throw new PageNotFoundException();
}
// Get information on user questions
$pagesize = qa_opt('page_size_activity');
$count = (int)@$userpoints['aposts'];
$questions = array_slice($questions, 0, $pagesize);
$usershtml = qa_userids_handles_html($questions, false);
// Prepare content for theme
$qa_content = qa_content_prepare(true);
if (count($questions))
$qa_content['title'] = qa_lang_html_sub('profile/answers_by_x', $this->userhtml);
else
$qa_content['title'] = qa_lang_html_sub('profile/no_answers_by_x', $this->userhtml);
// Recent questions by this user
$qa_content['q_list']['form'] = array(
'tags' => 'method="post" action="' . qa_self_html() . '"',
'hidden' => array(
'code' => qa_get_form_security_code('vote'),
),
);
$qa_content['q_list']['qs'] = array();
$htmldefaults = qa_post_html_defaults('Q');
$htmldefaults['whoview'] = false;
$htmldefaults['avatarsize'] = 0;
$htmldefaults['ovoteview'] = true;
$htmldefaults['answersview'] = false;
foreach ($questions as $question) {
$options = qa_post_html_options($question, $htmldefaults);
$options['voteview'] = qa_get_vote_view('A', false, false);
$qa_content['q_list']['qs'][] = qa_other_to_q_html_fields($question, $loginuserid, qa_cookie_get(),
$usershtml, null, $options);
}
$qa_content['page_links'] = qa_html_page_links(qa_request(), $start, $pagesize, $count, qa_opt('pages_prev_next'));
// Sub menu for navigation in user pages
$ismyuser = isset($loginuserid) && $loginuserid == (QA_FINAL_EXTERNAL_USERS ? $this->userid : $useraccount['userid']);
$qa_content['navigation']['sub'] = qa_user_sub_navigation($handle, 'answers', $ismyuser);
return $qa_content;
}
/**
* Return the HTML to display for the handle, and if we're using external users, determine the userid.
*
* @param string $handle
* @throws PageNotFoundException
*/
private function userHtml($handle)
{
if (QA_FINAL_EXTERNAL_USERS) {
$this->userid = qa_handle_to_userid($handle);
if (!isset($this->userid)) { // check the user exists
throw new PageNotFoundException();
}
$usershtml = qa_get_users_html(array($this->userid), false, qa_path_to_root(), true);
$this->userhtml = @$usershtml[$this->userid];
} else
$this->userhtml = qa_html($handle);
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Controllers\User;
use Q2A\Auth\NoPermissionException;
use Q2A\Middleware\Auth\InternalUsersOnly;
use Q2A\Middleware\Auth\MinimumUserLevel;
require_once QA_INCLUDE_DIR . 'db/users.php';
require_once QA_INCLUDE_DIR . 'db/selects.php';
require_once QA_INCLUDE_DIR . 'app/users.php';
require_once QA_INCLUDE_DIR . 'app/format.php';
class UsersList extends \Q2A\Controllers\BaseController
{
public function __construct()
{
parent::__construct();
$this->addMiddleware(new InternalUsersOnly(), array('newest', 'special', 'blocked'));
$this->addMiddleware(new MinimumUserLevel(QA_USER_LEVEL_MODERATOR), array('blocked'));
}
/**
* Display top users page (ordered by points)
* @return array $qa_content
*/
public function top()
{
// callables to fetch user data
$fetchUsers = function($start, $pageSize) {
return array(
qa_opt('cache_userpointscount'),
qa_db_select_with_pending(qa_db_top_users_selectspec($start, $pageSize))
);
};
$userScore = function($user) {
return qa_html(qa_format_number($user['points'], 0, true));
};
$qa_content = $this->rankedUsersContent($fetchUsers, $userScore);
$qa_content['title'] = empty($qa_content['ranking']['items'])
? qa_lang_html('main/no_active_users')
: qa_lang_html('main/highest_users');
$qa_content['ranking']['sort'] = 'points';
return $qa_content;
}
/**
* Display newest users page
*
* @return array $qa_content
* @throws NoPermissionException
*/
public function newest()
{
// check we have permission to view this page (moderator or above)
if (qa_user_permit_error('permit_view_new_users_page')) {
throw new NoPermissionException();
}
// callables to fetch user data
$fetchUsers = function($start, $pageSize) {
return array(
qa_opt('cache_userpointscount'),
qa_db_select_with_pending(qa_db_newest_users_selectspec($start, $pageSize))
);
};
$userDate = function($user) {
$when = qa_when_to_html($user['created'], 7);
return $when['data'];
};
$qa_content = $this->rankedUsersContent($fetchUsers, $userDate);
$qa_content['title'] = empty($qa_content['ranking']['items'])
? qa_lang_html('main/no_active_users')
: qa_lang_html('main/newest_users');
$qa_content['ranking']['sort'] = 'date';
return $qa_content;
}
/**
* Display special users page (admins, moderators, etc)
*
* @return array $qa_content
* @throws NoPermissionException
*/
public function special()
{
// check we have permission to view this page (moderator or above)
if (qa_user_permit_error('permit_view_special_users_page')) {
throw new NoPermissionException();
}
// callables to fetch user data
$fetchUsers = function($start, $pageSize) {
// here we fetch *all* users to get the total instead of a separate query; there are unlikely to be many special users
$users = qa_db_select_with_pending(qa_db_users_from_level_selectspec(QA_USER_LEVEL_EXPERT));
return array(count($users), $users);
};
$userLevel = function($user) {
return qa_html(qa_user_level_string($user['level']));
};
$qa_content = $this->rankedUsersContent($fetchUsers, $userLevel);
$qa_content['title'] = qa_lang_html('users/special_users');
$qa_content['ranking']['sort'] = 'level';
return $qa_content;
}
/**
* Display blocked users page
* @return array $qa_content
*/
public function blocked()
{
// callables to fetch user data
$fetchUsers = function($start, $pageSize) {
list($totalUsers, $users) = qa_db_select_with_pending(
qa_db_selectspec_count(qa_db_users_with_flag_selectspec(QA_USER_FLAGS_USER_BLOCKED)),
qa_db_users_with_flag_selectspec(QA_USER_FLAGS_USER_BLOCKED, $start, $pageSize)
);
return array($totalUsers['count'], $users);
};
$userLevel = function($user) {
return qa_html(qa_user_level_string($user['level']));
};
$qa_content = $this->rankedUsersContent($fetchUsers, $userLevel);
$qa_content['title'] = empty($qa_content['ranking']['items'])
? qa_lang_html('users/no_blocked_users')
: qa_lang_html('users/blocked_users');
$qa_content['ranking']['sort'] = 'level';
return $qa_content;
}
/**
* Fetch $qa_content array for a set of ranked users.
* @param callable $fnUsersAndCount Function that returns the list of users for a page and the user total.
* @param callable $fnUserScore Function that returns the "score" (points, date, etc) that will be displayed.
* @return array $qa_content
*/
private function rankedUsersContent($fnUsersAndCount, $fnUserScore)
{
// get the users to display on this page
$request = qa_request();
$start = qa_get_start();
$pageSize = qa_opt('page_size_users');
list($totalUsers, $users) = $fnUsersAndCount($start, $pageSize);
// get userids and handles of retrieved users
$usersHtml = qa_userids_handles_html($users);
// prepare content for theme
$content = qa_content_prepare();
$content['ranking'] = array(
'items' => array(),
'rows' => ceil($pageSize / qa_opt('columns_users')),
'type' => 'users',
// 'sort' => '',
);
foreach ($users as $user) {
if (QA_FINAL_EXTERNAL_USERS) {
$avatarHtml = qa_get_external_avatar_html($user['userid'], qa_opt('avatar_users_size'), true);
} else {
$avatarHtml = qa_get_user_avatar_html($user['flags'], $user['email'], $user['handle'],
$user['avatarblobid'], $user['avatarwidth'], $user['avatarheight'], qa_opt('avatar_users_size'), true);
}
$content['ranking']['items'][] = array(
'avatar' => $avatarHtml,
'label' => $usersHtml[$user['userid']],
'score' => $fnUserScore($user),
'raw' => $user,
);
}
$content['page_links'] = qa_html_page_links($request, $start, $pageSize, $totalUsers, qa_opt('pages_prev_next'));
// set the canonical url based on possible pagination
$params = $start > 0 ? array('start' => $start) : null;
$content['canonical'] = qa_path_html($request, $params, qa_opt('site_url'));
$content['navigation']['sub'] = qa_users_sub_navigation();
return $content;
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Database;
use PDO;
use PDOStatement;
use Q2A\Database\Exceptions\ReadingFromEmptyResultException;
class DbResult
{
private $stmt;
public function __construct(PDOStatement $stmt)
{
$this->stmt = $stmt;
}
/**
* Return the first row from the results as an array of [column name] => [column value].
* @return array|null
*/
public function fetchNextAssoc()
{
$data = $this->stmt->fetch();
return $data === false ? null : $data;
}
/**
* Return the first row from the results as an array of [column name] => [column value]. If no value is fetched
* from the database then an exception is thrown.
* @return array|null
* @throws ReadingFromEmptyResultException
*/
public function fetchNextAssocOrFail()
{
return $this->getResultOrFail($this->stmt->fetch(PDO::FETCH_ASSOC));
}
/**
* Return the data as an associative array.
* @param string $key Unique field to use as the array key for each row.
* @param string $value Field to use as the value (for key=>value format instead of the default key=>row)
* @return array
*/
public function fetchAllAssoc($key = null, $value = null)
{
$data = array();
while (($row = $this->stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
if (isset($key)) {
$data[$row[$key]] = isset($value) ? $row[$value] : $row;
} else {
$data[] = isset($value) ? $row[$value] : $row;
}
}
return $data;
}
/**
* Return a specific cell from the results. Typically used with (single-row, single-column) aggregate queries. If
* no value is fetched form the database return null.
* @param $col 0-indexed column to select from (defaults to first column).
* @return string
*/
public function fetchOneValue($col = 0)
{
$data = $this->stmt->fetchColumn($col);
return $data === false ? null : $data;
}
/**
* Return a specific cell from the results. Typically used with (single-row, single-column) aggregate queries. If
* no value is fetched from the database then an exception is thrown.
* @param $col 0-indexed column to select from (defaults to first column).
* @return string
* @throws ReadingFromEmptyResultException
*/
public function fetchOneValueOrFail($col = 0)
{
return $this->getResultOrFail($this->stmt->fetchColumn($col));
}
/**
* Return a numbered array containing the specified column.
* @param $col 0-indexed column to select from (defaults to first column).
* @return array
*/
public function fetchAllValues($col = 0)
{
return $this->stmt->fetchAll(PDO::FETCH_COLUMN, $col);
}
/**
* Return data or optionally throw an exception, if data is false (empty).
* @param mixed $data
* @return mixed
* @throws ReadingFromEmptyResultException
*/
private function getResultOrFail($data)
{
if ($data === false) {
throw new ReadingFromEmptyResultException('Reading one value from empty results');
}
return $data;
}
/**
* Number of rows found (SELECT queries) or rows affected (UPDATE/INSERT/DELETE queries).
* @return int
*/
public function rowCount()
{
return $this->stmt->rowCount();
}
/**
* Return true if the number of rows found (SELECT queries) or rows affected (UPDATE/INSERT/DELETE queries) is 0
* and false otherwise.
* @return int
*/
public function isEmpty()
{
return $this->stmt->rowCount() === 0;
}
/**
* Obtain the raw PDOStatement object.
* @return PDOStatement
*/
public function getPDOStatement()
{
return $this->stmt;
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Database\Exceptions;
use Q2A\Exceptions\FatalErrorException;
class ReadingFromEmptyResultException extends FatalErrorException
{
/**
* ReadingFromEmptyResultException constructor.
*
* @param string $message
*/
public function __construct($message = 'There has been an attempt to read from an empty database result')
{
parent::__construct($message);
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Exceptions;
use Exception;
class ErrorMessageException extends Exception
{
/**
* ErrorMessageException constructor.
*
* @param string $message
*/
public function __construct($message = 'Error message exception.')
{
parent::__construct($message);
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Exceptions;
use Exception;
use Q2A\Http\Exceptions\PageNotFoundException;
require_once QA_INCLUDE_DIR . 'app/format.php';
class ExceptionHandler
{
public function __construct()
{
}
public function handle(Exception $exception)
{
if ($exception instanceof FatalErrorException) {
$this->handleFatalErrorException($exception);
} elseif ($exception instanceof PageNotFoundException) {
return $this->handlePageNotFoundException($exception);
} elseif ($exception instanceof ErrorMessageException) {
return $this->handleErrorMessageException($exception);
} else {
return $this->handleUnknownException($exception);
}
}
private function handleFatalErrorException(FatalErrorException $exception)
{
qa_fatal_error($exception->getMessage());
}
private function handlePageNotFoundException(PageNotFoundException $exception)
{
header('HTTP/1.0 404 Not Found');
$qa_content = $this->handleErrorMessageException($exception);
$qa_content['suggest_next'] = qa_html_suggest_qs_tags(qa_using_tags());
return $qa_content;
}
private function handleErrorMessageException(ErrorMessageException $exception)
{
$qa_content = qa_content_prepare();
$qa_content['error'] = $exception->getMessage();
return $qa_content;
}
private function handleUnknownException(Exception $exception)
{
return $this->handleErrorMessageException(new ErrorMessageException($exception->getMessage()));
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Exceptions;
use Exception;
class FatalErrorException extends Exception
{
/**
* FatalErrorException constructor.
*
* @param string $message
*/
public function __construct($message = 'Fatal error exception.')
{
parent::__construct($message);
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Http\Exceptions;
use Q2A\Exceptions\ErrorMessageException;
class PageNotFoundException extends ErrorMessageException
{
/**
* PageNotFoundException constructor.
*
* @param string $message
*/
public function __construct($message = null)
{
if ($message === null) {
$message = qa_lang_html('main/page_not_found');
}
parent::__construct($message);
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Http;
class Route
{
/** @var string */
private $id;
/** @var string */
private $httpMethod;
/** @var string */
private $routePath;
/** @var string */
private $controller;
/** @var string */
private $action;
/** @var array */
private $parameters;
public function __construct($id = null, $httpMethod = null, $routePath = null, $controller = null, $action = null)
{
$this->id = $id;
$this->httpMethod = strtoupper($httpMethod);
$this->routePath = $routePath;
$this->controller = $controller;
$this->action = $action;
$this->parameters = array();
}
/**
* @return string
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getHttpMethod()
{
return $this->httpMethod;
}
/**
* @return string
*/
public function getRoutePath()
{
return $this->routePath;
}
/**
* @return string
*/
public function getController()
{
return $this->controller;
}
/**
* @return string
*/
public function getAction()
{
return $this->action;
}
/**
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
/**
* Bind actual request parameters to the route, replacing any existing ones.
*
* @param array $parameters
*/
public function setParameters($parameters)
{
$this->parameters = $parameters;
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Http;
class Router
{
/** @var Route[] */
protected $routes = array();
/** @var array */
private $paramsConverter;
/** @var string */
private $httpMethod;
public function __construct()
{
$this->paramsConverter = array(
'{str}' => '([^/]+)',
'{int}' => '([0-9]+)',
);
$this->httpMethod = strtoupper($_SERVER['REQUEST_METHOD']);
}
public function addRoute($id, $httpMethod, $routePath, $class, $func)
{
$this->routes[] = new Route($id, $httpMethod, $routePath, $class, $func);
}
/**
* Return the route definition that matches the given request. If none is found then null is
* returned.
*
* @param string $request Request that will be looked for a match
*
* @return Route|null
*/
public function match($request)
{
foreach ($this->routes as $route) {
if ($route->getHttpMethod() !== $this->httpMethod) {
continue;
}
$pathRegex = $this->buildPathRegex($route->getRoutePath());
if (preg_match($pathRegex, $request, $matches)) {
$route->setParameters(array_slice($matches, 1));
return $route;
}
}
return null;
}
/**
* Build the final regular expression to match the request. This method replaces all the
* parameters' placeholders with the given regular expression.
*
* @param string $routePath Route that might have placeholders
*
* @return string
*/
private function buildPathRegex($routePath)
{
return '#^' . strtr($routePath, $this->paramsConverter) . '$#';
}
/**
* Return the HTTP method of the current request.
*
* @return string
*/
public function getHttpMethod()
{
return $this->httpMethod;
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Middleware\Auth;
use Q2A\Auth\InternalUsersOnlyException;
use Q2A\Middleware\BaseMiddleware;
class InternalUsersOnly extends BaseMiddleware
{
/**
* Throw an exception if the current configuration is set to external users.
*
* @throws InternalUsersOnlyException
*/
public function handle()
{
if (QA_FINAL_EXTERNAL_USERS) {
throw new InternalUsersOnlyException('User accounts are handled by external code');
}
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Middleware\Auth;
use Q2A\Auth\NoPermissionException;
use Q2A\Middleware\BaseMiddleware;
class MinimumUserLevel extends BaseMiddleware
{
private $minimumUserLevel;
/**
* MinimumUserLevel constructor.
*
* @param int $minimumUserLevel Minimum user level allowed to perform the action
*/
public function __construct($minimumUserLevel)
{
$this->minimumUserLevel = $minimumUserLevel;
}
/**
* Throw an exception if the current configuration is set to external users.
*
* @throws NoPermissionException
*/
public function handle()
{
if (qa_get_logged_in_level() < $this->minimumUserLevel) {
throw new NoPermissionException();
}
}
}
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace Q2A\Middleware;
abstract class BaseMiddleware
{
abstract public function handle();
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment