diff --git a/.env b/.env
index 25331ca93..8f36e198c 100755
--- a/.env
+++ b/.env
@@ -20,7 +20,7 @@ FUEL_ENV=production
BOOL_SEND_EMAILS=false
#SYSTEM_EMAIL=
', $notice).'";');
+ }
+
+ Js::push_inline('var CONTEXT = "login";');
- Css::push_group(['core', 'login']);
- Js::push_group(['angular', 'materia']);
+ Event::trigger('request_login', $direct_login);
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
->set('title', 'Login')
->set('page_type', 'login');
- $this->theme->set_partial('content', 'partials/login')
- ->set('redirect', urlencode($redirect));
+ Css::push_group(['login']);
+ Js::push_group(['react', 'login']);
}
public function post_login()
@@ -84,25 +115,18 @@ public function get_profile()
{
if (\Service_User::verify_session() !== true)
{
- Session::set_flash('notice', 'Please log in to view this page.');
- Response::redirect(Router::get('login').'?redirect='.URI::current());
+ Session::set('redirect_url', URI::current());
+ Session::set_flash('notice', 'Please log in to view your profile.');
+ Response::redirect(Router::get('login'));
+ return;
}
- Css::push_group(['core', 'profile']);
-
- Js::push_group(['angular', 'materia', 'student']);
-
- // to properly fix the date display, we need to provide the raw server date for JS to access
- $server_date = date_create('now', timezone_open('UTC'))->format('D, d M Y H:i:s');
- Js::push_inline("var DATE = '$server_date'");
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
+ $this->theme->get_template()->set('title', 'My Profile');
- $this->theme->get_template()
- ->set('title', 'Profile')
- ->set('page_type', 'user profile');
-
- $this->theme->set_partial('footer', 'partials/angular_alert');
- $this->theme->set_partial('content', 'partials/user/profile')
- ->set('me', \Model_User::find_current());
+ Css::push_group(['profile']);
+ Js::push_group(['react', 'profile']);
}
/**
@@ -113,20 +137,18 @@ public function get_settings()
{
if (\Service_User::verify_session() !== true)
{
- Session::set_flash('notice', 'Please log in to view this page.');
- Response::redirect(Router::get('login').'?redirect='.URI::current());
+ Session::set('redirect_url', URI::current());
+ Session::set_flash('notice', 'Please log in to view your profile settings.');
+ Response::redirect(Router::get('login'));
+ return;
}
- Css::push_group(['core', 'profile']);
- Js::push_group(['angular', 'materia', 'student']);
-
- $this->theme->get_template()
- ->set('title', 'Settings')
- ->set('page_type', 'user profile settings');
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
+ $this->theme->get_template()->set('title', 'Settings');
- $this->theme->set_partial('footer', 'partials/angular_alert');
- $this->theme->set_partial('content', 'partials/user/settings')
- ->set('me', \Model_User::find_current());
+ Css::push_group(['profile']);
+ Js::push_group(['react', 'settings']);
}
}
diff --git a/fuel/app/classes/controller/widgets.php b/fuel/app/classes/controller/widgets.php
index cbcfd4bc8..31ccf4e59 100644
--- a/fuel/app/classes/controller/widgets.php
+++ b/fuel/app/classes/controller/widgets.php
@@ -7,6 +7,7 @@
class Controller_Widgets extends Controller
{
use Trait_CommonControllerTemplate;
+ use Trait_Supportinfo;
/**
* Catalog page to show all the available widgets
@@ -15,16 +16,13 @@ class Controller_Widgets extends Controller
*/
public function get_index()
{
- Css::push_group(['core', 'widget_catalog']);
-
- Js::push_group(['angular', 'ng-animate', 'materia']);
-
- $this->theme->get_template()
- ->set('title', 'Widget Catalog')
- ->set('page_type', 'catalog');
-
- $this->theme->set_partial('content', 'partials/widget/catalog');
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
+ $this->theme->get_template()->set('title', 'Widget Catalog');
$this->theme->set_partial('meta', 'partials/responsive');
+
+ Css::push_group(['catalog']);
+ Js::push_group(['react', 'catalog']);
}
public function get_all()
@@ -48,18 +46,17 @@ public function get_detail()
$demo = $widget->meta_data['demo'];
- Css::push_group(['widget_detail', 'core']);
- Js::push_group(['angular', 'hammerjs', 'jquery', 'materia', 'student']);
+ Js::push_inline('var NO_AUTHOR = "'.\Materia\Perm_Manager::does_user_have_role(['no_author']).'";');
+ Js::push_inline('var WIDGET_HEIGHT = "'.$widget->height.'";');
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
->set('title', 'Widget Details')
->set('page_type', 'widget');
- $this->theme->set_partial('content', 'partials/widget/detail');
-
- $this->theme->set_partial('meta', 'partials/responsive');
- $this->theme->set_partial('footer', 'partials/angular_alert');
- $this->_disable_browser_cache = true;
+ Css::push_group(['detail']);
+ Js::push_group(['react', 'detail']);
}
/**
@@ -117,20 +114,21 @@ public function get_guide(string $type)
break;
}
- Css::push_group(['core', 'guide']);
- Js::push_group(['angular', 'materia']);
+ Js::push_inline('var NAME = "'.$widget->name.'";');
+ Js::push_inline('var TYPE = "'.$type.'";');
+ Js::push_inline('var HAS_PLAYER_GUIDE = "'.( ! empty($widget->player_guide)).'";');
+ Js::push_inline('var HAS_CREATOR_GUIDE = "'.( ! empty($widget->creator_guide)).'";');
+ Js::push_inline('var DOC_PATH = "'.Config::get('materia.urls.engines').$widget->dir.$guide.'";');
+
+
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
->set('title', $title)
->set('page_type', 'guide');
- $this->theme->set_partial('meta', 'partials/responsive');
-
- $this->theme->set_partial('content', 'partials/widget/guide_doc')
- ->set('name', $widget->name)
- ->set('type', $type)
- ->set('has_player_guide', ! empty($widget->player_guide))
- ->set('has_creator_guide', ! empty($widget->creator_guide))
- ->set('doc_path', Config::get('materia.urls.engines').$widget->dir.$guide);
+ Css::push_group(['guide']);
+ Js::push_group(['react', 'guides']);
}
/**
@@ -161,8 +159,10 @@ public function get_edit($inst_id)
}
/**
- * Listing of all widgets i have rights to
+ * Listing of all widgets a given user has rights to
*/
+ // this exists as an alternative to the next one for use with the React component
+ // once we're sure that is good enough, replace the action after this one with this action, rename it, remove unnecessary routes etc.
public function get_mywidgets()
{
if (\Service_User::verify_session() !== true)
@@ -170,19 +170,15 @@ public function get_mywidgets()
Session::set('redirect_url', URI::current());
Session::set_flash('notice', 'Please log in to view your widgets.');
Response::redirect(Router::get('login'));
+ return;
}
- Css::push_group(['core', 'my_widgets']);
- Js::push_group(['angular', 'jquery', 'materia', 'author', 'tablock', 'spinner', 'jqplot', 'my_widgets', 'dataTables']);
-
- Js::push_inline('var IS_STUDENT = '.(\Service_User::verify_session(['basic_author', 'super_user']) ? 'false;' : 'true;'));
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
+ $this->theme->get_template()->set('title', 'My Widgets');
- $this->theme->get_template()
- ->set('title', 'My Widgets')
- ->set('page_type', 'my_widgets');
-
- $this->theme->set_partial('footer', 'partials/angular_alert');
- $this->theme->set_partial('content', 'partials/my_widgets');
+ Css::push_group(['my_widgets']);
+ Js::push_group(['react', 'my_widgets']);
}
public function get_play_demo()
@@ -201,6 +197,8 @@ public function action_play_widget($inst_id = false)
public function action_play_embedded($inst_id = false)
{
+ // if routed from the legacy LTI URL, the instance id is available as a GET parameter
+ if ( ! $inst_id && \Input::get('widget') ) $inst_id = \Input::get('widget');
// context_id isolates attempt count for an class so a user's attempt limit is reset per course
Session::set('context_id', \Input::post('context_id'));
return $this->_play_widget($inst_id, false, true);
@@ -237,9 +235,6 @@ public function get_preview_widget($inst_id, $is_embedded = false)
$this->display_widget($inst, false, $is_embedded);
}
}
-
- Css::push_group('widget_play');
-
}
/* ============================== PROTECTED ================================== */
@@ -248,103 +243,113 @@ public function get_preview_widget($inst_id, $is_embedded = false)
protected function show_editor($title, $widget, $inst_id=null)
{
$this->_disable_browser_cache = true;
- Css::push_group(['core', 'widget_create']);
- Js::push_group(['angular', 'materia', 'author']);
- if ( ! empty($widget->creator) && preg_match('/\.swf$/', $widget->creator))
- {
- // add swfobject if it's needed
- Js::push_group('swfobject');
- }
+ Js::push_inline('var WIDGET_HEIGHT = "'.$widget->height.'";');
+ Js::push_inline('var WIDGET_WIDTH = "'.$widget->width.'";');
+
+
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
->set('title', $title)
- ->set('page_type', 'create');
+ ->set('page_type', 'widget');
- $this->theme->set_partial('footer', 'partials/angular_alert');
- $this->theme->set_partial('content', 'partials/widget/create')
- ->set('widget', $widget)
- ->set('inst_id', $inst_id);
+ Css::push_group(['widget_create']);
+ Js::push_group(['react', 'createpage']);
}
protected function draft_not_playable()
{
$this->_disable_browser_cache = true;
+
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
->set('title', 'Draft Not Playable')
->set('page_type', '');
- $this->theme->set_partial('content', 'partials/widget/draft_not_playable');
- $this->theme->set_partial('footer', 'partials/angular_alert');
+ Js::push_group(['react', 'draft_not_playable']);
+ Css::push_group(['login']);
- Js::push_group(['angular', 'materia']);
+ $this->add_inline_info();
}
- protected function retired()
+ protected function retired(bool $is_embedded = false)
{
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
->set('title', 'Retired Widget')
->set('page_type', '');
- $this->theme->set_partial('content', 'partials/widget/retired');
- $this->theme->set_partial('footer', 'partials/angular_alert');
+ Js::push_group(['react', 'retired']);
+ Css::push_group(['login']);
- Js::push_group(['angular', 'materia']);
+ Js::push_inline('var IS_EMBEDDED = "'.$is_embedded.'";');
}
protected function no_attempts(object $inst, bool $is_embedded)
{
$this->_disable_browser_cache = true;
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
->set('title', 'Widget Unavailable')
->set('page_type', 'login');
- $this->theme->set_partial('footer', 'partials/angular_alert');
- $this->theme->set_partial('content', 'partials/widget/no_attempts')
- ->set('classes', 'widget')
- ->set('attempts', $inst->attempts)
- ->set('scores_path', '/scores'.($is_embedded ? '/embed' : '').'/'.$inst->id)
-
- ->set('summary', $this->theme->view('partials/widget/summary')
- ->set('type',$inst->widget->name)
- ->set('name', $inst->name)
- ->set('icon', Config::get('materia.urls.engines')."{$inst->widget->dir}img/icon-92.png"));
+ Js::push_inline('var ATTEMPTS = "'.$inst->attempts.'";');
+ Js::push_inline('var WIDGET_ID = "'.$inst->id.'";');
+ Js::push_inline('var IS_EMBEDDED = "'.$is_embedded.'";');
+ Js::push_inline('var NAME = "'.$inst->name.'";');
+ Js::push_inline('var ICON_DIR = "'.Config::get('materia.urls.engines').$inst->widget->dir.'";');
- Js::push_group(['angular', 'materia']);
// The styles for this are in login, should probably be moved?
Css::push_group('login');
+ Js::push_group(['react', 'no_attempts']);
}
protected function no_permission()
{
$this->_disable_browser_cache = true;
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
->set('title', 'Permission Denied')
->set('page_type', '');
- $this->theme->set_partial('footer', 'partials/angular_alert');
- $this->theme->set_partial('content', 'partials/nopermission');
-
- Js::push_group(['angular', 'materia']);
+ Js::push_group(['react', 'no_permission']);
+ Css::push_group('no_permission');
+ $this->add_inline_info();
}
protected function embedded_only($inst)
{
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
->set('title', 'Widget Unavailable')
->set('page_type', 'login');
- $this->theme->set_partial('footer', 'partials/angular_alert');
- $this->theme->set_partial('content', 'partials/widget/embedded_only')
- ->set('classes', 'widget')
+ $theme_overrides = \Event::Trigger('before_embedded_only', '', 'array');
+ if ($theme_overrides)
+ {
+ $this->theme->set_template('layouts/react');
+ $this->theme->get_template()
+ ->set('title', 'Login')
+ ->set('page_type', 'login');
- ->set('summary', $this->theme->view('partials/widget/summary')
- ->set('type',$inst->widget->name)
- ->set('name', $inst->name)
- ->set('icon', Config::get('materia.urls.engines')."{$inst->widget->dir}img/icon-92.png"));
+ Css::push_group([$theme_overrides[0]['css']]);
+ Js::push_group(['react', $theme_overrides[0]['js']]);
+ }
+ else
+ {
+ Js::push_group(['react', 'embedded_only']);
+ // The styles for this are in login, should probably be moved?
+ Css::push_group('login');
+ }
- Js::push_group(['angular', 'materia']);
- // The styles for this are in login, should probably be moved?
- Css::push_group('login');
+ Js::push_inline('var NAME = "'.$inst->name.'";');
+ Js::push_inline('var ICON_DIR = "'.Config::get('materia.urls.engines').$inst->widget->dir.'";');
}
protected function _play_widget($inst_id = false, $demo=false, $is_embedded=false)
@@ -371,9 +376,6 @@ protected function _play_widget($inst_id = false, $demo=false, $is_embedded=fals
$inst = Materia\Widget_Instance_Manager::get($inst_id);
if ( ! $inst) throw new HttpNotFoundException;
- // Disable header if embedded, prior to setting the widget view or any login/error screens
- if ($is_embedded) $this->_header = 'partials/header_empty';
-
if ( ! $is_embedded && $inst->embedded_only) return $this->embedded_only($inst);
// display a login
@@ -384,9 +386,9 @@ protected function _play_widget($inst_id = false, $demo=false, $is_embedded=fals
$status = $inst->status($context_id);
- if ( ! $status['open']) return $this->build_widget_login('Widget Unavailable', $inst_id);
+ if ( ! $status['open']) return $this->build_widget_login('Widget Unavailable', $inst_id, $is_embedded);
if ( ! $demo && $inst->is_draft) return $this->draft_not_playable();
- if ( ! $demo && ! $inst->widget->is_playable) return $this->retired();
+ if ( ! $demo && ! $inst->widget->is_playable) return $this->retired($is_embedded);
if ( ! $status['has_attempts']) return $this->no_attempts($inst, $is_embedded);
if (isset($_GET['autoplay']) && $_GET['autoplay'] === 'false') return $this->pre_embed_placeholder($inst);
@@ -420,6 +422,7 @@ protected function build_widget_login($login_title = null, $inst_id = null, $is_
$server_date = date_create('now', timezone_open('UTC'))->format('D, d M Y H:i:s');
// ===================== RENDER ==========================
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
->set('title', $login_title ?: 'Login')
->set('page_type', 'login');
@@ -429,39 +432,69 @@ protected function build_widget_login($login_title = null, $inst_id = null, $is_
if ($is_open)
{
// fire an event prior to deciding which theme to render
- $alt = \Event::Trigger('before_widget_login');
// if something came back as a result of the event being triggered, use that instead of the default
- $theme = $alt ?: 'partials/widget/login';
- $content = $this->theme->set_partial('content', $theme);
- $content
- ->set('user', __('user'))
- ->set('pass', __('password'))
- ->set('links', __('links'))
- ->set('title', $login_title)
- ->set('date', $server_date)
- ->set('preview', $is_preview);
+ // theme_overrides object should include an array with a js and css index
+ // these specify a) the react page to render and b) its associated css
+ $theme_overrides = \Event::Trigger('before_widget_login', '', 'array');
+ if ($theme_overrides)
+ {
+ $this->theme->set_template('layouts/react');
+ $this->theme->get_template()
+ ->set('title', 'Login')
+ ->set('page_type', 'login');
+
+ Css::push_group([$theme_overrides[0]['css']]);
+ Js::push_group(['react', $theme_overrides[0]['js']]);
+ }
+ else
+ {
+ $this->theme->set_template('layouts/react');
+ $this->theme->get_template()
+ ->set('title', 'Login')
+ ->set('page_type', 'login');
+
+ Css::push_group(['login']);
+ Js::push_group(['react', 'login']);
+ }
+
+ Js::push_inline('var EMBEDDED = '.($is_embedded ? 'true' : 'false').';');
+ Js::push_inline('var ACTION_LOGIN = "'.\Router::get('login').'";');
+ Js::push_inline('var ACTION_REDIRECT = "'.urlencode(URI::current()).'";');
+ Js::push_inline('var LOGIN_USER = "'.\Lang::get('login.user').'";');
+ Js::push_inline('var LOGIN_PW = "'.\Lang::get('login.password').'";');
+ Js::push_inline('var CONTEXT = "widget";');
+ Js::push_inline('var NAME = "'.$inst->name.'";');
+ Js::push_inline('var WIDGET_NAME = "'.$inst->widget->name.'";');
+ Js::push_inline('var IS_PREVIEW = "'.$is_preview.'";');
+ Js::push_inline('var ICON_DIR = "'.Config::get('materia.urls.engines').$inst->widget->dir.'";');
+
+ // condense login links into a string with delimiters to be embedded as a JS global
+ $link_items = [];
+ foreach (\Lang::get('login.links') as $a)
+ {
+ $link_items[] = $a['href'].'***'.$a['title'];
+ }
+ $login_links = implode('@@@', $link_items);
+ Js::push_inline('var LOGIN_LINKS = "'.urlencode($login_links).'";');
}
else
{
- $content = $this->theme->set_partial('content', 'partials/widget/closed');
- $content
- ->set('msg', __('user'))
- ->set('date', $server_date)
- ->set_safe('availability', $desc);
- }
+ Js::push_inline('var IS_EMBEDDED = '.($is_embedded ? 'true' : 'false').';');
+ Js::push_inline('var NAME = "'.$inst->name.'";');
+ Js::push_inline('var WIDGET_NAME = "'.$inst->widget->name.'";');
+ Js::push_inline('var ICON_DIR = "'.Config::get('materia.urls.engines').$inst->widget->dir.'";');
- // add widget summary
- $content->set('classes', 'widget '.($is_preview ? 'preview' : ''))
- ->set('summary', $this->theme->view('partials/widget/summary')
- ->set('type',$inst->widget->name)
- ->set('name', $inst->name)
- ->set('icon', Config::get('materia.urls.engines')."{$inst->widget->dir}img/icon-92.png")
- ->set_safe('avail', $summary));
+ Js::push_inline('var SUMMARY = "'.$summary.'";');
+ Js::push_inline('var DESC = "'.$desc.'";');
- if ($is_embedded) $this->_header = 'partials/header_empty';
+ $this->theme->set_template('layouts/react');
+ $this->theme->get_template()
+ ->set('title', 'Widget Unavailable')
+ ->set('page_type', 'login');
- Js::push_group(['angular', 'materia', 'student']);
- Css::push_group('login');
+ Css::push_group(['login']);
+ Js::push_group(['react', 'closed']);
+ }
}
protected function build_widget_login_messages($inst)
@@ -473,13 +506,14 @@ protected function build_widget_login_messages($inst)
// Build the open/close dates for display
if ($status['opens'])
{
- $start_string = ''.date($format, (int) $inst->open_at).'';
- $start_sec = '{{ time('.((int) $inst->open_at * 1000).') }}';
+ // $start_string = ''.date($format, (int) $inst->open_at).'';
+ $start_string = date($format, (int) $inst->open_at);
+ $start_sec = date('h:i A', (int) $inst->open_at * 1000);
}
if ($status['closes'])
{
- $end_string = ''.date($format, (int) $inst->close_at).'';
- $end_sec = '{{ time('.((int) $inst->close_at * 1000).') }}';
+ $end_string = date($format, (int) $inst->close_at);
+ $end_sec = date('h:i A', (int) $inst->close_at * 1000);
}
// finish the actual messages to the user
@@ -507,47 +541,44 @@ protected function build_widget_login_messages($inst)
protected function display_widget(\Materia\Widget_Instance $inst, $play_id=false, $is_embedded=false)
{
- Css::push_group(['core', 'widget_play']);
- Js::push_group(['angular', 'materia', 'student']);
- if ($is_embedded) $this->_header = 'partials/header_empty';
- if ( ! empty($inst->widget->player) && preg_match('/\.swf$/', $inst->widget->player))
- {
- // add swfobject if it's needed
- Js::push_group('swfobject');
- }
-
Js::push_inline('var PLAY_ID = "'.$play_id.'";');
+ Js::push_inline('var DEMO_ID = "'.$inst->id.'";');
+ Js::push_inline('var WIDGET_HEIGHT = "'.$inst->widget->height.'";');
+ Js::push_inline('var WIDGET_WIDTH = "'.$inst->widget->width.'";');
+ Js::push_inline('var STATIC_CROSSDOMAIN = "'.Config::get('materia.urls.static').'";');
+ Js::push_inline('var BASE_URL = "'.Uri::base().'";');
+ Js::push_inline('var WIDGET_URL = "'.Config::get('materia.urls.engines').'";');
+ Js::push_inline('var MEDIA_URL = "'.Config::get('materia.urls.media').'";');
+
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
->set('title', $inst->name.' '.$inst->widget->name)
->set('page_type', 'widget')
->set('html_class', $is_embedded ? 'embedded' : '' );
- $this->theme->set_partial('footer', 'partials/angular_alert');
- $this->theme->set_partial('content', 'partials/widget/play')
- ->set('inst_id', $inst->id);
+ Css::push_group(['playpage']);
+ Js::push_group(['react', 'playpage']);
}
protected function pre_embed_placeholder($inst)
{
$this->_disable_browser_cache = true;
+ $this->theme = Theme::instance();
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
- ->set('title', 'Widget Unavailable')
- ->set('page_type', 'login');
+ ->set('title', $inst->name.' '.$inst->widget->name)
+ ->set('page_type', 'widget');
$uri = URI::current();
$context = strpos($uri, 'play/') != false ? 'play' : 'embed';
- $this->theme->set_partial('footer', 'partials/angular_alert');
- $this->theme->set_partial('content', 'partials/widget/pre_embed_placeholder')
- ->set('classes', 'widget')
- ->set('inst_id', $inst->id)
- ->set('context', $context)
- ->set('summary', $this->theme->view('partials/widget/summary')
- ->set('type',$inst->widget->name)
- ->set('name', $inst->name)
- ->set('icon', Config::get('materia.urls.engines')."{$inst->widget->dir}img/icon-92.png"));
-
- Js::push_group(['angular', 'materia']);
+ Js::push_inline('var INST_ID = "'.$inst->id.'";');
+ Js::push_inline('var CONTEXT = "'.$context.'";');
+ Js::push_inline('var NAME = "'.$inst->name.'";');
+ Js::push_inline('var ICON_DIR = "'.Config::get('materia.urls.engines').$inst->widget->dir.'";');
+
+ Js::push_group(['react', 'pre_embed']);
Css::push_group(['login','pre_embed_placeholder']);
}
}
diff --git a/fuel/app/classes/materia/api/v1.php b/fuel/app/classes/materia/api/v1.php
index c20e3afb8..b173b7be3 100644
--- a/fuel/app/classes/materia/api/v1.php
+++ b/fuel/app/classes/materia/api/v1.php
@@ -10,7 +10,7 @@
- Verb Last
- Use a underscore between the item and the verb
EX: gameInstance_get, gameInstance_create, gameInstance_edit, gameInstance_copy
-Availible Verbs:
+Available Verbs:
- get (retrive a value)
- create (create/save a new value)
- delete (remove a value)
@@ -42,18 +42,33 @@ static public function widgets_get_by_type($type)
return Widget_Manager::get_widgets([], $type);
}
- static public function widget_instances_get($inst_ids = null)
+ static public function widget_instances_get($inst_ids = null, bool $deleted = false, $load_qset = false)
{
// get all my instances - must be logged in
if (empty($inst_ids))
{
if (\Service_User::verify_session() !== true) return []; // shortcut to returning noting
- return Widget_Instance_Manager::get_all_for_user(\Model_User::find_current_id());
+ return Widget_Instance_Manager::get_all_for_user(\Model_User::find_current_id(), $load_qset);
}
// get specific instances - no log in required
if ( ! is_array($inst_ids)) $inst_ids = [$inst_ids]; // convert string into array of items
- return Widget_Instance_Manager::get_all($inst_ids);
+ return Widget_Instance_Manager::get_all($inst_ids, $load_qset, false, $deleted);
+ }
+
+/**
+ * Takes a page number, and returns objects containing the total_num_pages and
+ * widget instances that are visible to the user.
+ *
+ * @param page_number The page to be requested. By default it is set to 1.
+ *
+ * @return array of objects containing total_num_pages and widget instances that are visible to the user.
+ */
+ static public function widget_paginate_instances_get($page_number = 0)
+ {
+ if (\Service_User::verify_session() !== true) return Msg::no_login();
+ $data = Widget_Instance_Manager::get_paginated_for_user(\Model_User::find_current_id(), $page_number);
+ return $data;
}
/**
@@ -63,7 +78,7 @@ static public function widget_instance_delete($inst_id)
{
if ( ! Util_Validator::is_valid_hash($inst_id)) return Msg::invalid_input($inst_id);
if (\Service_User::verify_session() !== true) return Msg::no_login();
- if ( ! static::has_perms_to_inst($inst_id, [Perm::FULL])) return Msg::no_perm();
+ if ( ! static::has_perms_to_inst($inst_id, [Perm::FULL]) && ! Perm_Manager::is_support_user()) return Msg::no_perm();
if ( ! ($inst = Widget_Instance_Manager::get($inst_id))) return false;
return $inst->db_remove();
}
@@ -123,7 +138,7 @@ static private function has_perms_to_inst($inst_id, $perms)
static public function widget_instance_copy(string $inst_id, string $new_name, bool $copy_existing_perms = false)
{
if (\Service_User::verify_session() !== true) return Msg::no_login();
- if ( ! static::has_perms_to_inst($inst_id, [Perm::FULL])) return Msg::no_perm();
+ if ( ! static::has_perms_to_inst($inst_id, [Perm::FULL]) && ! Perm_Manager::is_support_user()) return Msg::no_perm();
$inst = Widget_Instance_Manager::get($inst_id, true);
try
@@ -131,7 +146,7 @@ static public function widget_instance_copy(string $inst_id, string $new_name, b
// retain access - if true, grant access to the copy to all original owners
$current_user_id = \Model_User::find_current_id();
$duplicate = $inst->duplicate($current_user_id, $new_name, $copy_existing_perms);
- return $duplicate->id;
+ return $duplicate;
}
catch (\Exception $e)
{
@@ -196,6 +211,7 @@ static public function widget_instance_new($widget_id=null, $name=null, $qset=nu
* Save and existing instance
*
* @param int $inst_id
+ * @param string $name
* @param object $qset
* @param bool $is_draft Whether the widget is being saved as a draft
* @param int $open_at
@@ -229,6 +245,12 @@ static public function widget_instance_update($inst_id=null, $name=null, $qset=n
{
$inst->qset = $qset;
}
+ else
+ {
+ // if the qset is not explicitly provided, assume it is not being updated
+ // if $inst->qset is populated it will be saved to the db as a new qset version - which isn't necessary
+ $inst->qset = (object) ['version' => null, 'data' => null];
+ }
if ( ! empty($name))
{
if ($inst->name != $name)
@@ -638,12 +660,14 @@ static public function guest_widget_instance_scores_get($inst_id, $play_id)
* [quickStats] contains attempts, scores, currentPlayers, avScore, replays
* [playLogs] a log of all scores recoreded
*/
- static public function play_logs_get($inst_id, $semester = 'all', $year = 'all')
+ static public function play_logs_get($inst_id, $semester = 'all', $year = 'all', $page_number=1)
{
- if ( ! Util_Validator::is_valid_hash($inst_id)) return Msg::invalid_input($inst_id);
+ if ( ! Util_Validator::is_valid_hash($inst_id)) return Msg::invalid_input($inst_id);
if (\Service_User::verify_session() !== true) return Msg::no_login();
if ( ! static::has_perms_to_inst($inst_id, [Perm::VISIBLE, Perm::FULL])) return Msg::no_perm();
- return Session_Play::get_by_inst_id($inst_id, $semester, $year);
+
+ $data = Session_Play::get_by_inst_id_paginated($inst_id, $semester, $year, $page_number);
+ return $data;
}
/**
@@ -817,6 +841,7 @@ static public function questions_get($ids=null, $type=null) // remote_getQuestio
return Widget_Question_Manager::get_users_questions(\Model_User::find_current_id(), $type);
}
}
+
static public function play_storage_data_save($play_id, $data)
{
$inst = self::_get_instance_for_play_id($play_id);
@@ -887,7 +912,9 @@ static public function user_get($user_ids = null)
//no user ids provided, return current user
if ($user_ids === null)
{
- $results = \Model_User::find_current();
+ //$results = \Model_User::find_current();
+ $me = \Model_User::find_current_id();
+ $results = \Model_User::find($me);
$results = $results->to_array();
}
else
@@ -895,6 +922,7 @@ static public function user_get($user_ids = null)
if (empty($user_ids) || ! is_array($user_ids)) return Msg::invalid_input();
//user ids provided, get all of the users with the given ids
$me = \Model_User::find_current_id();
+
foreach ($user_ids as $id)
{
if (Util_Validator::is_pos_int($id))
@@ -1087,21 +1115,36 @@ static public function notifications_get()
return $return_array;
}
- static public function notification_delete($note_id)
+ static public function notification_delete($note_id, $delete_all)
{
if ( ! \Service_User::verify_session()) return Msg::no_login();
$user = \Model_User::find_current();
- $note = \Model_Notification::query()
+ if ($delete_all)
+ {
+ $notes = \Model_Notification::query()
+ ->where('to_id', $user->id)
+ ->get();
+
+ foreach ($notes as $note)
+ {
+ $note->delete();
+ }
+ return true;
+ }
+ if ($note_id)
+ {
+ $note = \Model_Notification::query()
->where('id', $note_id)
->where('to_id', $user->id)
->get();
- if ($note)
- {
- $note[$note_id]->delete();
- return true;
+ if ($note)
+ {
+ $note[$note_id]->delete();
+ return true;
+ }
}
return false;
}
@@ -1122,7 +1165,7 @@ static private function _validate_play_id($play_id)
{
if ($play->get_by_id($play_id))
{
- if ($play->is_valid == 1)
+ if (intval($play->is_valid) == 1)
{
$play->update_elapsed(); // update the elapsed time
return $play;
diff --git a/fuel/app/classes/materia/perm.php b/fuel/app/classes/materia/perm.php
index 00c82a8c7..29af26de9 100644
--- a/fuel/app/classes/materia/perm.php
+++ b/fuel/app/classes/materia/perm.php
@@ -62,10 +62,10 @@ abstract class Perm
const GIVE_SHARE = 75;
// group rights only
- /** @const Has rights to access manger interface */
- const AUTHORACCESS = 80;
- /** @const Has rights to administer users */
- const ADMINISTRATOR = 85;
- /** @const Has super user rights to do anything */
+ /** @const Standard author access level. Can edit/publish/modify and share widgets without restrictions. */
+ const BASICAUTHOR = 80;
+ /** @const Elevated access level. Grants access to instance and user admin interface. Can review user settings, ownership, and play history, and access/modify instances and their settings. */
+ const SUPPORTUSER = 85;
+ /** @const Super-elevated access level. Can do anything. */
const SUPERUSER = 90;
}
diff --git a/fuel/app/classes/materia/perm/manager.php b/fuel/app/classes/materia/perm/manager.php
index ee0349904..bf3e703da 100644
--- a/fuel/app/classes/materia/perm/manager.php
+++ b/fuel/app/classes/materia/perm/manager.php
@@ -34,16 +34,24 @@ static public function create_role($role_name = '')
*/
static public function is_super_user()
{
- $login_hash = \Session::get('login_hash');
- $key = 'is_super_user_'.$login_hash;
- $has_role = (\Fuel::$is_cli === true && ! \Fuel::$is_test) || \Session::get($key, false);
+ // @TODO this was previously creating a local session object storing the value returned from this
+ // The session caching has been removed due to issues related to the cache when the role is added or revoked
+ // Ideally we can still find a way to cache this and make it more performant!!
+ return (\Fuel::$is_cli === true && ! \Fuel::$is_test) || self::does_user_have_role([\Materia\Perm_Role::SU]);
+
+ }
- if ( ! $has_role)
- {
- $has_role = self::does_user_have_role([\Materia\Perm_Role::SU]);
- \Session::set($key, $has_role);
- }
- return $has_role;
+ /**
+ * Check if user is currently logged in as a support user
+ *
+ * @return boolean wheter or not the current user has the support role as defined by Perm_Role class
+ */
+ static public function is_support_user(): bool
+ {
+ // @TODO this was previously creating a local session object storing the value returned from this
+ // The session caching has been removed due to issues related to the cache when the role is added or revoked
+ // Ideally we can still find a way to cache this and make it more performant!!
+ return (\Fuel::$is_cli === true && ! \Fuel::$is_test) || self::does_user_have_role([\Materia\Perm_Role::SUPPORT]);
}
/**
@@ -261,12 +269,11 @@ static public function get_user_roles($user_id = 0)
->as_object();
$current_id = \Model_User::find_current_id();
+ $roles = [];
// return logged in user's roles if id is 0 or less, non su users can only use this method
if ($user_id <= 0 || $user_id == $current_id)
{
- $roles = [];
-
$results = $q->where('m.user_id', $current_id)
->execute();
@@ -533,7 +540,10 @@ static public function clear_all_perms_for_object($object_id, $object_type)
}
/**
- * Gets an array of object id's that a user has permissions for matching any of the requested permissions.
+ * Gets an array of object ids of a given type that a user has EXPLICIT permissions to that matches the perms provided.
+ * (!!!) NOTE: Previously, this method would also return IMPLICITLY available objects based on the user's role (if elevated).
+ * This is no longer the case. If IMPLICITLY available objects are required, use get_all_objects_for_elevated_user_role
+ *
* If an object has any of the requested permissions, it will be returned.
* Perm_Manager->get_all_objects_for_users($user->user_id, \Materia\Perm::INSTANCE, [\Materia\Perm::SHARE]);
*
@@ -551,43 +561,64 @@ static public function get_all_objects_for_user($user_id, $object_type, $perms)
// WHERE id IN (5, 6) whould match ids that ***START*** with 5 or 6
foreach ($perms as &$value) $value = (string) $value;
- // ====================== GET THE USERS ROLE PERMISSIONS ============================
- // build a subquery that gets any roles the user has
- $subquery_role_ids = \DB::select('role_id')
- ->from('perm_role_to_user')
- ->where('user_id', $user_id);
-
- // get any perms that users roles have
- $roles_perms = \DB::select('perm')
- ->from('perm_role_to_perm')
- ->where('role_id', 'IN', $subquery_role_ids)
+ // ==================== GET USER's EXPLICIT PERMISSSION ==============================
+ // get objects that the user has direct access to
+ $objects = \DB::select('object_id')
+ ->from('perm_object_to_user')
+ ->where('object_type', $object_type)
+ ->where('user_id', $user_id)
->where('perm', 'IN', $perms)
- ->execute();
-
- // Only super_user has role perm 30 -- get all assets/widgets
- if ($roles_perms->count() != 0)
- {
- $objects = \DB::select('id')
- ->from($object_type == Perm::ASSET ? 'asset' : 'widget_instance')
- ->execute()
- ->as_array('id', 'id');
- }
- else
- {
- // ==================== GET USER's EXPLICIT PERMISSSION ==============================
- // get objects that the user has direct access to
- $objects = \DB::select('object_id')
- ->from('perm_object_to_user')
- ->where('object_type', $object_type)
- ->where('user_id', $user_id)
- ->where('perm', 'IN', $perms)
- ->execute()
- ->as_array('object_id', 'object_id');
- }
+ ->execute()
+ ->as_array('object_id', 'object_id');
return $objects;
}
}
+ /**
+ * Gets an array of object ids that a user has permissions to access EXCLUSIVELY based on an elevated role
+ * This requires the user has a role with elevated perms, and that the group rights associated with those perms are present in the perm_role_to_perm table
+ * Currently, the role must be Perm::SUPPORTUSER or Perm::SUPERUSER
+ *
+ * Perm_Manager->get_all_objects_for_users($user->user_id, \Materia\Perm::INSTANCE);
+ *
+ * @param int User ID the get permissions for
+ * @param int Object type as defined in Perm constants
+ */
+ static public function get_all_objects_for_elevated_user_role($user_id, $object_type)
+ {
+ $objects = [];
+ $user_is_admin_or_su = false;
+
+ // ====================== GET THE USERS ROLE PERMISSIONS ============================
+ // build a subquery that gets any roles the user has
+ $subquery_role_ids = \DB::select('role_id')
+ ->from('perm_role_to_user')
+ ->where('user_id', $user_id);
+
+ // get any perms that users roles have
+ $roles_perms = \DB::select('perm')
+ ->from('perm_role_to_perm')
+ ->where('role_id', 'IN', $subquery_role_ids)
+ ->execute();
+
+
+ // verify that perms returned from perm_role_to_perm table are elevated
+ // this means either Perm::SUPPORTUSER (85) or Perm::SUPERUSER (90)
+ foreach ($roles_perms as $role)
+ {
+ if (in_array([Perm::SUPPORTUSER, Perm::SUPERUSER], $role['perm'])) $user_is_admin_or_su = true;
+ }
+
+ if ($user_is_admin_or_su == true)
+ {
+ $objects = \DB::select('id')
+ ->from($object_type == Perm::ASSET ? 'asset' : 'widget_instance')
+ ->execute()
+ ->as_array('id', 'id');
+ }
+ return $objects;
+ }
+
/**
* Counts the number of users with perms to a given object
* to an object (used by Widget_Asset_Manager.can_asset_be_deleted)
@@ -646,9 +677,9 @@ static public function get_all_users_explicit_perms(string $object_id, int $obje
$all_perms = self::get_all_users_with_perms_to($object_id, $object_type);
- // if the current user is a super user, append their user's perms into the results
- // super users don't get explicit perms to things set in the db, so we'll fake it here
- $cur_user_perms = self::is_super_user() ? [Perm::SUPERUSER, null] : $all_perms[$current_user_id];
+ // if the current user is a super user or support user, append their user's perms into the results
+ // super users and support users don't get explicit perms to objects set in the db, so we'll fake it here
+ $cur_user_perms = self::is_super_user() ? [Perm::SUPERUSER, null] : (self::is_support_user() ? [Perm::FULL, null] : $all_perms[$current_user_id]);
return [
'user_perms' => [$current_user_id => $cur_user_perms],
diff --git a/fuel/app/classes/materia/perm/role.php b/fuel/app/classes/materia/perm/role.php
index 05d24cb27..e9fc04b8e 100644
--- a/fuel/app/classes/materia/perm/role.php
+++ b/fuel/app/classes/materia/perm/role.php
@@ -29,6 +29,7 @@ class Perm_Role
const NOAUTH = 'no_author';
const AUTHOR = 'basic_author';
const SU = 'super_user';
+ const SUPPORT = 'support_user';
public $id;
public $name;
diff --git a/fuel/app/classes/materia/session/play.php b/fuel/app/classes/materia/session/play.php
index 8c559990f..679da49ab 100644
--- a/fuel/app/classes/materia/session/play.php
+++ b/fuel/app/classes/materia/session/play.php
@@ -260,6 +260,20 @@ public static function get_by_inst_id($inst_id, $semester='all', $year='all')
return $plays;
}
+ public static function get_by_inst_id_paginated($inst_id, $semester='all', $year='all', $page_number=1)
+ {
+ $items_per_page = 100;
+ $data = self::get_by_inst_id($inst_id, $semester, $year);
+ $total_num_pages = ceil(sizeof($data) / $items_per_page);
+ $offset = $items_per_page * ($page_number - 1);
+ $page = array_slice($data, $offset, $items_per_page);
+ $data = [
+ 'total_num_pages' => $total_num_pages,
+ 'pagination' => $page,
+ ];
+ return $data;
+ }
+
/**
* NEEDS DOCUMENTAION
*
diff --git a/fuel/app/classes/materia/session/playdataexporter.php b/fuel/app/classes/materia/session/playdataexporter.php
index 9fd2ffffa..ab4bb9a6a 100644
--- a/fuel/app/classes/materia/session/playdataexporter.php
+++ b/fuel/app/classes/materia/session/playdataexporter.php
@@ -243,7 +243,7 @@ protected static function full_event_log($inst, $semesters)
$semester,
$play_event->type,
$play_event->item_id,
- str_replace(["\r","\n", ','], '', $play_event->text), // sanitize commas and newlines to keep CSV formatting intact
+ is_string($play_event->text) ? str_replace(["\r","\n", ','], '', $play_event->text) : '', // sanitize commas and newlines to keep CSV formatting intact
$play_event->value,
$play_event->game_time,
$play_event->created_at
@@ -307,7 +307,7 @@ protected static function full_event_log($inst, $semesters)
foreach ($csv_questions as $question)
{
// Sanitize newlines and commas, as they break CSV formatting
- $sanitized_question_text = str_replace(["\r","\n", ','], '', $question['text']);
+ $sanitized_question_text = is_string($question['text']) ? str_replace(["\r","\n", ','], '', $question['text']) : '';
$csv_question_text .= "\r\n{$question['question_id']},{$question['id']},{$sanitized_question_text}";
foreach ($options as $key)
@@ -327,14 +327,14 @@ protected static function full_event_log($inst, $semesters)
foreach ($csv_answers as $answer)
{
// Sanitize newlines and commas, as they break CSV formatting
- $sanitized_answer_text = str_replace(["\r","\n", ','], '', $answer['text']);
+ $sanitized_answer_text = is_string($answer['text']) ? str_replace(["\r","\n", ','], '', $answer['text']) : '';
$csv_answer_text .= "\r\n{$answer['question_id']},{$answer['id']},{$sanitized_answer_text},{$answer['value']}";
}
$tempname = tempnam('/tmp', 'materia_raw_log_csv');
$zip = new \ZipArchive();
- $zip->open($tempname);
+ $zip->open($tempname, \ZipArchive::OVERWRITE);
$zip->addFromString('questions.csv', $csv_question_text);
$zip->addFromString('answers.csv', $csv_answer_text);
$zip->addFromString('logs.csv', $csv_playlog_text);
@@ -362,12 +362,12 @@ protected static function questions_and_answers($inst, $semesters)
foreach ($questions as $question)
{
- $sanitized_question = str_replace(["\r","\n", ','], '', $question->questions[0]['text']);
+ $sanitized_question = is_string($question->questions[0]['text']) ? str_replace(["\r","\n", ','], '', $question->questions[0]['text']) : '';
$sanitized_answers = [];
foreach ($question->answers as $answer)
{
- $sanitized_answer = str_replace(["\r","\n", ','], '', $answer['text']);
+ $sanitized_answer = is_string($answer['text']) ? str_replace(["\r","\n", ','], '', $answer['text']) : '';
array_push($sanitized_answers, $sanitized_answer);
}
@@ -426,7 +426,7 @@ protected static function referrer_urls($inst, $semesters)
$tempname = tempnam('/tmp', 'materia_raw_log_csv');
$zip = new \ZipArchive();
- $zip->open($tempname);
+ $zip->open($tempname, \ZipArchive::OVERWRITE);
$zip->addFromString('individual_referrers.csv', $csv_i);
$zip->addFromString('collective_referrers.csv', $csv_c);
$zip->close();
diff --git a/fuel/app/classes/materia/widget/asset.php b/fuel/app/classes/materia/widget/asset.php
index 9cf628cce..e831f25dc 100644
--- a/fuel/app/classes/materia/widget/asset.php
+++ b/fuel/app/classes/materia/widget/asset.php
@@ -16,11 +16,16 @@ class Widget_Asset
const MAP_TYPE_QUESTION = '2';
protected const MIME_TYPE_TO_EXTENSION = [
- 'image/png' => 'png',
- 'image/gif' => 'gif',
- 'image/jpeg' => 'jpg',
- 'audio/mpeg' => 'mp3',
- 'text/plain' => 'obj',
+ 'image/png' => 'png',
+ 'image/gif' => 'gif',
+ 'image/jpeg' => 'jpg',
+ 'audio/mp4' => 'm4a',
+ 'audio/x-m4a' => 'm4a',
+ 'audio/mpeg' => 'mp3',
+ 'audio/wav' => 'wav',
+ 'audio/wave' => 'wav',
+ 'audio/x-wav' => 'wav',
+ 'text/plain' => 'obj',
];
protected const MIME_TYPE_FROM_EXTENSION = [
@@ -28,8 +33,10 @@ class Widget_Asset
'gif' => 'image/gif',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
+ 'm4a' => 'audio/mp4',
'mp3' => 'audio/mpeg',
- 'obj' => 'text/plain',
+ 'wav' => 'audio/wav',
+ 'obj' => 'text/plain',
];
public $created_at = 0;
@@ -39,6 +46,7 @@ class Widget_Asset
public $file_size = '';
public $questions = [];
public $type = '';
+ public $is_deleted = 0;
protected $_storage_driver;
@@ -115,7 +123,9 @@ public function db_update()
'type' => $this->type,
'title' => $this->title,
'file_size' => $this->file_size,
- 'created_at' => time()
+ 'created_at' => time(),
+ 'is_deleted' => $this->is_deleted
+
])
->where('id','=',$this->id)
->execute();
diff --git a/fuel/app/classes/materia/widget/instance.php b/fuel/app/classes/materia/widget/instance.php
index f5a69c6b2..1291b2a8b 100644
--- a/fuel/app/classes/materia/widget/instance.php
+++ b/fuel/app/classes/materia/widget/instance.php
@@ -14,6 +14,7 @@ class Widget_Instance
public $embed_url = '';
public $is_student_made = false;
public $is_embedded = false;
+ public $is_deleted = false;
public $embedded_only = false;
public $student_access = false;
public $guest_access = false;
@@ -418,6 +419,35 @@ public function db_remove()
return true;
}
+ /**
+ * 'Undeletes' instance by updating is_deleted flag from 1 to 0
+ * @return bool true if successfully undeleted, false if unable to restore
+ */
+ public function db_undelete(): bool
+ {
+ if ( ! Util_Validator::is_valid_hash($this->id)) return false;
+
+ $current_user_id = \Model_User::find_current_id();
+
+ \DB::update('widget_instance')
+ ->set(['is_deleted' => '0', 'updated_at' => time()])
+ ->where('id', $this->id)
+ ->execute();
+
+ // store an activity log
+ $activity = new Session_Activity([
+ 'user_id' => $current_user_id,
+ 'type' => Session_Activity::TYPE_EDIT_WIDGET,
+ 'item_id' => $this->id,
+ 'value_1' => $this->name,
+ 'value_2' => $this->widget->id
+ ]);
+
+ $activity->db_store();
+
+ return true;
+ }
+
/**
* Creates a duplicate widget instance and optionally makes the current user the owner.
*
@@ -434,7 +464,17 @@ public function duplicate(int $owner_id, string $new_name = null, bool $copy_exi
$duplicate->id = 0; // mark as a new game
$duplicate->user_id = $owner_id; // set current user as owner in instance table
- if ( ! empty($new_name)) $duplicate->name = $new_name; // update name
+ // update name
+ if ( ! empty($new_name)) $duplicate->name = $new_name;
+
+ // these values aren't saved to the db - but the frontend will make use of them
+ $duplicate->clean_name = \Inflector::friendly_title($duplicate->name, '-', true);
+ $base_url = "{$duplicate->id}/{$duplicate->clean_name}";
+ $duplicate->preview_url = \Config::get('materia.urls.preview').$base_url;
+ $duplicate->play_url = $duplicate->is_draft === false ? \Config::get('materia.urls.play').$base_url : '';
+ $duplicate->embed_url = $duplicate->is_draft === false ? \Config::get('materia.urls.embed').$base_url : '';
+
+ $duplicate->created_at = time(); // manually update created_at, the actual value saved to the db is created in db_store
// if original widget is student made - verify if new owner is a student or not
// if they have a basic_author role or above, turn off the is_student_made flag
@@ -460,6 +500,9 @@ public function duplicate(int $owner_id, string $new_name = null, bool $copy_exi
$owners = [];
$viewers = [];
+ // Add current user
+ $owners[] = $owner_id;
+
foreach ($existing_perms['widget_user_perms'] as $user_id => $perm_obj)
{
list($perm) = $perm_obj;
@@ -597,6 +640,85 @@ public function lti_associations()
->get();
}
+ public function get_all_extra_attempts(): array
+ {
+ $semester = Semester::get_current_semester();
+
+ $result = \DB::select('id', 'user_id', 'context_id','extra_attempts')
+ ->from('user_extra_attempts')
+ ->where('inst_id', $this->id)
+ ->where('semester', $semester)
+ ->execute()
+ ->as_array();
+
+ return $result;
+ }
+
+ public function set_extra_attempts(int $user_id, int $extra_attempts, string $context_id, $id=null)
+ {
+ $semester = Semester::get_current_semester();
+
+ $result = [ 'user_id' => $user_id, 'success' => false ];
+
+ // we have an ID, update an existing row
+ if ($id != null)
+ {
+ if ($extra_attempts > 0)
+ {
+ \DB::update('user_extra_attempts')
+ ->value('extra_attempts', $extra_attempts)
+ ->value('context_id', $context_id)
+ ->where('id', '=', $id)
+ ->execute();
+
+ $result = [
+ 'user_id' => $user_id,
+ 'extra_attempts' => $extra_attempts,
+ 'context_id' => $context_id,
+ 'success' => true
+ ];
+ }
+ // delete existing row if attempts <= 0
+ else
+ {
+ \DB::delete('user_extra_attempts')
+ ->where('id', $id)
+ ->execute();
+
+ $result = [ 'user_id' => $user_id, 'success' => true ];
+ }
+ }
+ // no ID provided, add new row
+ else
+ {
+ // make sure extra attempts are > 0, otherwise no need to add
+ if ($extra_attempts > 0)
+ {
+ \DB::insert('user_extra_attempts')
+ ->set([
+ 'inst_id' => $this->id,
+ 'semester' => $semester,
+ 'user_id' => $user_id,
+ 'extra_attempts' => $extra_attempts,
+ 'context_id' => $context_id,
+ 'created_at' => time()
+ ])
+ ->execute();
+
+ $result = [
+ 'inst_id' => $this->id,
+ 'semester' => $semester,
+ 'user_id' => $user_id,
+ 'extra_attempts' => $extra_attempts,
+ 'context_id' => $context_id,
+ 'success' => true
+ ];
+ }
+ }
+
+ return $result;
+ }
+
public function export()
{
}
diff --git a/fuel/app/classes/materia/widget/instance/manager.php b/fuel/app/classes/materia/widget/instance/manager.php
index 8a68ab141..d6ef5f140 100644
--- a/fuel/app/classes/materia/widget/instance/manager.php
+++ b/fuel/app/classes/materia/widget/instance/manager.php
@@ -6,13 +6,15 @@ class Widget_Instance_Manager
{
public $validate = true;
- static public function get($inst_id, $load_qset=false, $timestamp=false)
+ // rolling back type expectations for now to resolve failing tests - this isn't a bad idea but it needs more focused attention
+ // static public function get(string $inst_id, bool $load_qset=false, $timestamp=false, bool $deleted=false)
+ static public function get($inst_id, $load_qset=false, $timestamp=false, $deleted=false)
{
- $instances = Widget_Instance_Manager::get_all([$inst_id], $load_qset, $timestamp);
+ $instances = Widget_Instance_Manager::get_all([$inst_id], $load_qset, $timestamp, $deleted);
return count($instances) > 0 ? $instances[0] : false;
}
- static public function get_all(Array $inst_ids, $load_qset=false, $timestamp=false)
+ static public function get_all(Array $inst_ids, $load_qset=false, $timestamp=false, bool $deleted=false): array
{
if ( ! is_array($inst_ids) || count($inst_ids) < 1) return [];
@@ -23,7 +25,7 @@ static public function get_all(Array $inst_ids, $load_qset=false, $timestamp=fal
$results = \DB::select()
->from('widget_instance')
->where('id', 'IN', $inst_ids)
- ->and_where('is_deleted', '=', '0')
+ ->and_where('is_deleted', '=', $deleted ? '1' : '0')
->order_by('created_at', 'desc')
->execute()
->as_array();
@@ -45,6 +47,7 @@ static public function get_all(Array $inst_ids, $load_qset=false, $timestamp=fal
'open_at' => $r['open_at'],
'close_at' => $r['close_at'],
'attempts' => $r['attempts'],
+ 'is_deleted' => (bool) $r['is_deleted'],
'embedded_only' => (bool) $r['embedded_only'],
'widget' => $widget,
]);
@@ -56,14 +59,44 @@ static public function get_all(Array $inst_ids, $load_qset=false, $timestamp=fal
return $instances;
}
- public static function get_all_for_user($user_id)
+ public static function get_all_for_user($user_id, $load_qset=false)
{
$inst_ids = Perm_Manager::get_all_objects_for_user($user_id, Perm::INSTANCE, [Perm::FULL, Perm::VISIBLE]);
- if ( ! empty($inst_ids)) return Widget_Instance_Manager::get_all($inst_ids);
+ if ( ! empty($inst_ids)) return Widget_Instance_Manager::get_all($inst_ids, $load_qset);
else return [];
}
+/**
+ * It takes a user ID and a page number, and returns an array of instances that the user has permission
+ * to see, along with the total number of pages
+ *
+ * @param user_id The user id of the user whose instances we want to get
+ * @param page_number The page number of the pagination.
+ *
+ * @return array of widget instances that are visible to the user.
+ */
+ public static function get_paginated_for_user($user_id, $page_number = 0)
+ {
+ $inst_ids = Perm_Manager::get_all_objects_for_user($user_id, Perm::INSTANCE, [Perm::FULL, Perm::VISIBLE]);
+ $displayable_inst = self::get_all($inst_ids);
+ $widgets_per_page = 80;
+ $total_num_pages = ceil(sizeof($displayable_inst) / $widgets_per_page);
+ $offset = $widgets_per_page * $page_number;
+ $has_next_page = $offset + $widgets_per_page < sizeof($displayable_inst) ? true : false;
+
+ // inst_ids corresponds to a single page's worth of instances
+ $displayable_inst = array_slice($displayable_inst, $offset, $widgets_per_page);
+
+ $data = [
+ 'pagination' => $displayable_inst,
+ ];
+
+ if ($has_next_page) $data['next_page'] = $page_number + 1;
+
+ return $data;
+ }
+
/**
* Checks to see if the given widget instance is locked by the current user.
*
@@ -101,4 +134,50 @@ public static function lock($inst_id)
// true if the lock is mine
return $locked_by == $me;
}
+
+ /**
+ * Gets all widget instances related to a given input, including id or name.
+ *
+ * @param input search input
+ *
+ * @return array of widget instances related to the given input
+ */
+ public static function get_search(string $input): array
+ {
+ $results = \DB::select()
+ ->from('widget_instance')
+ ->where('id', 'LIKE', "%$input%")
+ ->or_where('name', 'LIKE', "%$input%")
+ ->order_by('created_at', 'desc')
+ ->execute()
+ ->as_array();
+
+ $instances = [];
+ foreach ($results as $r)
+ {
+ $widget = new Widget();
+ $widget->get($r['widget_id']);
+ $student_access = Perm_Manager::accessible_by_students($r['id'], Perm::INSTANCE);
+ $inst = new Widget_Instance([
+ 'id' => $r['id'],
+ 'user_id' => $r['user_id'],
+ 'name' => $r['name'],
+ 'is_student_made' => (bool) $r['is_student_made'],
+ 'student_access' => $student_access,
+ 'guest_access' => (bool) $r['guest_access'],
+ 'is_draft' => (bool) $r['is_draft'],
+ 'created_at' => $r['created_at'],
+ 'open_at' => $r['open_at'],
+ 'close_at' => $r['close_at'],
+ 'attempts' => $r['attempts'],
+ 'is_deleted' => (bool) $r['is_deleted'],
+ 'embedded_only' => (bool) $r['embedded_only'],
+ 'widget' => $widget,
+ ]);
+
+ $instances[] = $inst;
+ }
+
+ return $instances;
+ }
}
diff --git a/fuel/app/classes/model/notification.php b/fuel/app/classes/model/notification.php
index 91942c021..1448caf52 100644
--- a/fuel/app/classes/model/notification.php
+++ b/fuel/app/classes/model/notification.php
@@ -74,7 +74,7 @@ public static function send_item_notification(int $from_user_id, int $to_user_id
$inst->db_get($inst_id, false);
$user_link = $from->first.' '.$from->last.' ('.$from->username.')';
- $widget_link = Html::anchor(\Config::get('materia.urls.root').'my-widgets#/'.$inst_id, $inst->name);
+ $widget_link = Html::anchor(\Config::get('materia.urls.root').'my-widgets#'.$inst_id, $inst->name);
$widget_name = $inst->name;
$widget_type = $inst->widget->name;
@@ -94,23 +94,23 @@ public static function send_item_notification(int $from_user_id, int $to_user_id
switch ($mode)
{
case 'disabled':
- $subject = "$user_link is no longer sharing \"$widget_name\" with you.";
+ $subject = "$user_link is no longer sharing \"$widget_name\" with you.";
break;
case 'changed':
- $subject = "$user_link changed your access to widget \"$widget_link\".
You now have $perm_string access.";
+ $subject = "$user_link changed your access to widget \"$widget_link\".
You now have $perm_string access.";
break;
case 'expired':
- $subject = "Your access to \"$widget_name\" has automatically expired.";
+ $subject = "Your access to \"$widget_name\" has automatically expired.";
break;
case 'deleted':
- $subject = "$user_link deleted $widget_type widget \"$widget_name\".";
+ $subject = "$user_link deleted $widget_type widget \"$widget_name\".";
break;
case 'access_request':
- $subject = "$user_link is requesting access to your widget \"$widget_name\".
The widget is currently being used within a course in your LMS.";
+ $subject = "$user_link is requesting access to your widget \"$widget_name\".
The widget is currently being used within a course in your LMS.";
$action = 'access_request';
break;
@@ -134,7 +134,8 @@ public static function send_item_notification(int $from_user_id, int $to_user_id
'is_read' => '0',
'subject' => $subject,
'avatar' => \Materia\Utils::get_avatar(50),
- 'action' => $action
+ 'action' => $action,
+ 'created_at' => time()
]);
$notification->save();
diff --git a/fuel/app/classes/model/user.php b/fuel/app/classes/model/user.php
index 50cf0cc4a..69d12fe28 100644
--- a/fuel/app/classes/model/user.php
+++ b/fuel/app/classes/model/user.php
@@ -94,6 +94,7 @@ static public function find_by_name_search($name)
$user_table = \Model_User::table();
$matches = \DB::select()
->from($user_table)
+ // Do not return super users or the current user // << why?
->where($user_table.'.id', 'NOT', \DB::expr('IN('.\DB::select($user_table.'.id')
->from($user_table)
->join('perm_role_to_user', 'LEFT')
@@ -158,6 +159,8 @@ public function to_array($custom = false, $recurse = false, $eav = false)
$array = parent::to_array($custom, $recurse, $eav);
$array['avatar'] = $avatar;
$array['is_student'] = \Materia\Perm_Manager::is_student($this->id);
+ $array['is_support_user'] = \Materia\Perm_Manager::does_user_have_role([\Materia\Perm_Role::SUPPORT], $this->id);
+ if (\Materia\Perm_Manager::does_user_have_role([\Materia\Perm_Role::SU], $this->id)) $array['is_super_user'] = true;
return $array;
}
diff --git a/fuel/app/classes/service/user.php b/fuel/app/classes/service/user.php
index 49a2f0826..9feb169c2 100644
--- a/fuel/app/classes/service/user.php
+++ b/fuel/app/classes/service/user.php
@@ -109,7 +109,9 @@ public static function get_played_inst_info($user_id)
'p.created_at',
'p.elapsed',
'p.is_complete',
- 'p.percent'
+ 'p.percent',
+ 'p.auth',
+ 'p.context_id'
)
->from(['log_play', 'p'])
->join(['widget_instance', 'i'])
diff --git a/fuel/app/classes/trait/commoncontrollertemplate.php b/fuel/app/classes/trait/commoncontrollertemplate.php
index c32f090a6..0b02488b0 100644
--- a/fuel/app/classes/trait/commoncontrollertemplate.php
+++ b/fuel/app/classes/trait/commoncontrollertemplate.php
@@ -8,13 +8,11 @@ trait Trait_CommonControllerTemplate
{
use Trait_Analytics;
- protected $_header = 'partials/header';
protected $_disable_browser_cache = false;
public function before()
{
$this->theme = Theme::instance();
- $this->theme->set_template('layouts/main');
}
public function after($response)
@@ -22,13 +20,7 @@ public function after($response)
// If no response object was returned by the action,
if (empty($response) or ! $response instanceof Response)
{
- // render the defined template
- $me = Model_User::find_current();
-
- $this->theme->set_partial('header', $this->_header)->set('me', $me);
-
$this->insert_analytics();
-
$response = Response::forge(Theme::instance()->render());
}
@@ -39,7 +31,6 @@ public function after($response)
}
$this->inject_common_js_constants();
- Css::push_group('core');
return parent::after($response);
}
diff --git a/fuel/app/classes/trait/supportinfo.php b/fuel/app/classes/trait/supportinfo.php
new file mode 100644
index 000000000..84395d602
--- /dev/null
+++ b/fuel/app/classes/trait/supportinfo.php
@@ -0,0 +1,20 @@
+ $_ENV['MEMCACHED_PORT'] ?? 11211,
'weight' => 100],
],
- ],
-
-);
+ ]
+];
diff --git a/fuel/app/config/config.php b/fuel/app/config/config.php
index 2650e05f5..dcc197469 100644
--- a/fuel/app/config/config.php
+++ b/fuel/app/config/config.php
@@ -321,6 +321,7 @@
*/
'language' => array(
'login',
+ 'support'
),
),
diff --git a/fuel/app/config/css.php b/fuel/app/config/css.php
index cc8af545f..60a1c9000 100644
--- a/fuel/app/config/css.php
+++ b/fuel/app/config/css.php
@@ -1,7 +1,7 @@
[
- 'admin' => [
- $static_css.'admin.css'
- ],
- 'widget_play' => [
- $static_css.'widget-play.css',
- $static_css.'ng-modal.css'
- ],
+ 'homepage' => [$webpack.'css/homepage.css'],
+ 'user-admin' => [$webpack.'css/user-admin.css'],
+ 'support' => [$webpack.'css/support.css'],
+ 'catalog' => [$webpack.'css/catalog.css'],
+ 'detail' => [$webpack.'css/detail.css'],
+ 'playpage' => [$webpack.'css/player-page.css'],
'lti' => [
- $static_css.'util-lti-picker.css',
- ],
- 'my_widgets' => [
- $static_css.'my-widgets.css',
- $cdnjs.'jqPlot/1.0.9/jquery.jqplot.min.css',
- $static_css.'ui-lightness/jquery-ui-1.8.21.custom.css',
- $static_css.'jquery.dataTables.css',
- $static_css.'ng-modal.css'
- ],
- 'widget_create' => [
- $static_css.'widget-create.css',
- $static_css.'ng-modal.css'
- ],
- 'widget_detail' => [
- $static_css.'widget-detail.css',
- $static_css.'ng-modal.css'
- ],
- 'widget_catalog' => [
- $static_css.'widget-catalog.css',
- ],
- 'profile' => [
- $static_css.'profile.css',
- ],
- 'login' => [
- $static_css.'login.css',
- ],
- 'scores' => [
- $cdnjs.'jqPlot/1.0.9/jquery.jqplot.min.css',
- $static_css.'scores.css',
- ],
- 'pre_embed_placeholder' => [
- $static_css.'widget-embed-placeholder.css'
- ],
- 'embed_scores' => [
- $static_css.'scores.css',
- ],
+ $webpack.'css/lti.css',
+ $webpack.'css/lti-select-item.css',
+ $webpack.'css/lti-error.css',
+ ],
+ 'my_widgets' => [$webpack.'css/my-widgets.css'],
+ 'widget_create' => [$webpack.'css/creator-page.css'],
+ 'profile' => [$webpack.'css/profile.css'],
+ 'login' => [$webpack.'css/login.css'],
+ 'scores' => [$webpack.'css/scores.css'],
+ 'pre_embed_placeholder' => [$webpack.'css/pre-embed-common-styles.css'],
+ 'embed_scores' => [$webpack.'css/scores.css'],
'question_import' => [
- $static_css.'jquery.dataTables.css',
- $static_css.'util-question-import.css',
- ],
- 'qset_history' => [
- $static_css.'util-qset-history.css',
- ],
- 'rollback_dialog' => [
- $static_css.'util-rollback-confirm.css'
- ],
- 'media_import' => [
- $static_css.'util-media-import.css'
- ],
- 'help' => [
- $static_css.'help.css',
- ],
- 'errors' => [
- $static_css.'errors.css',
- ],
- 'core' => [
- $static_css.'core.css',
- ],
+ $vendor.'jquery.dataTables.min.css',
+ $webpack.'css/util-question-import.css',
+ $webpack.'css/question-importer.css',
+ ],
+ 'questionimport' => [$webpack.'css/question-importer.css'],
+ 'qset_history' => [$webpack.'css/qset-history.css'],
+ 'rollback_dialog' => [$webpack.'css/util-rollback-confirm.css'],
+ 'media_import' => [$webpack.'css/media.css'],
+ 'help' => [$webpack.'css/help.css'],
'fonts' => [
- $g_fonts.'css?family=Kameron:700&text=0123456789%25',
- $g_fonts.'css?family=Lato:300,400,700,700italic,900&v2',
- ],
- 'guide' => [
- $static_css.'widget-guide.css',
- ],
+ $g_fonts.'css2?family=Kameron:wght@700&display=block',
+ $g_fonts.'css2?family=Lato:ital,wght@0,300;0,400;0,700;0,900;1,700&display=block',
+ ],
+ 'guide' => [$webpack.'css/guides.css'],
+ 'draft-not-playable' => [$webpack.'css/draft-not-playable.css'],
+ '404' => [$webpack.'css/404.css'],
+ '500' => [$webpack.'css/500.css'],
+ 'no_permission' => [$webpack.'css/no-permission.css']
],
-];
+];
\ No newline at end of file
diff --git a/fuel/app/config/development/materia.php b/fuel/app/config/development/materia.php
new file mode 100644
index 000000000..cec4350ad
--- /dev/null
+++ b/fuel/app/config/development/materia.php
@@ -0,0 +1,34 @@
+ false, // disable email in dev
+
+ 'urls' => [
+ // append port 8008 for dev
+ // simulates loading from a pass-through cdn
+ // No port is specified so 8080 is picked by default
+ 'static' => $simulated_cdn_url,
+ 'engines' => $simulated_cdn_url.'widget/',
+ 'js_css' => $assets_exist ? $simulated_cdn_url : '//127.0.0.1:8080/',
+ ],
+
+ /**
+ * Allow browser based widget uploads by administrators
+ */
+ 'enable_admin_uploader' => true,
+
+ // Storage driver can be overridden from env here
+ // s3 uses fakes3 on dev
+ 'asset_storage_driver' => 'file',
+
+ 'asset_storage' => [
+ 's3' => [
+ 'endpoint' => 'http://fakes3:10001',
+ 'bucket' => 'fake_bucket', // bucket to store original user uploads
+ ],
+ ]
+];
\ No newline at end of file
diff --git a/fuel/app/config/development/routes.php b/fuel/app/config/development/routes.php
index daecffa8e..91595862d 100644
--- a/fuel/app/config/development/routes.php
+++ b/fuel/app/config/development/routes.php
@@ -1,5 +1,10 @@
'site/404',
+ '500' => 'site/500',
+
// Route for testing what Materia looks like using the embed code
'test/external/(:alnum)(/.*)?' => 'widgets/test/external/$1',
diff --git a/fuel/app/config/file.php b/fuel/app/config/file.php
index 831d47e2c..5f98baa65 100644
--- a/fuel/app/config/file.php
+++ b/fuel/app/config/file.php
@@ -12,13 +12,11 @@
'basedir' => APPPATH,
'areas' => [
-
- 'media' => [
+ 'media' => [
'basedir' => realpath(APPPATH.'media').DS,
- 'extensions' => ['jpg', 'jpeg', 'png', 'gif', 'wav', 'mp3', 'obj'],
+ 'extensions' => ['jpg', 'jpeg', 'png', 'gif', 'wav', 'mp3', 'obj', 'm4a'],
'url' => DOCROOT . 'media',
]
],
);
-
diff --git a/fuel/app/config/js.php b/fuel/app/config/js.php
index db1eb85d7..582187e15 100644
--- a/fuel/app/config/js.php
+++ b/fuel/app/config/js.php
@@ -1,38 +1,46 @@
'asset_hash.js.json',
- 'remove_group_duplicates' => true,
-
'groups' => [
- 'materia' => [$static.'materia.js'],
- 'angular' => [$cdnjs.'angular.js/1.8.0/angular.min.js'],
- 'ng-animate' => [$cdnjs.'angular-animate/1.8.0/angular-animate.min.js'],
- 'jquery' => [$cdnjs.'jquery/3.5.1/jquery.min.js'],
- 'admin' => [$static.'admin.js'],
- 'author' => [$static.'author.js'],
- 'student' => [$static.'student.js'],
- 'dataTables' => [$static.'vendor/datatables/jquery.dataTables.min.js'],
- 'jquery_ui' => [$cdnjs.'jqueryui/1.12.1/jquery-ui.min.js'],
- 'labjs' => [$static.'vendor/labjs/LAB.min.js'],
- 'spinner' => [$static.'vendor/spin.min.js', $static.'spin.jquery.js'],
- 'hammerjs' => [$static.'vendor/hammer.min.js'],
- 'swfobject' => [$static.'vendor/swfobject/swfobject.js'],
-
- 'jqplot' => [
- $cdnjs.'jqPlot/1.0.9/jquery.jqplot.min.js',
- $cdnjs.'jqPlot/1.0.9/plugins/jqplot.barRenderer.min.js',
- $cdnjs.'jqPlot/1.0.9/plugins/jqplot.canvasTextRenderer.min.js',
- $cdnjs.'jqPlot/1.0.9/plugins/jqplot.canvasAxisTickRenderer.min.js',
- $cdnjs.'jqPlot/1.0.9/plugins/jqplot.categoryAxisRenderer.min.js',
- $cdnjs.'jqPlot/1.0.9/plugins/jqplot.cursor.min.js',
- $cdnjs.'jqPlot/1.0.9/plugins/jqplot.highlighter.min.js',
+ 'login' => [$webpack.'js/login.js'],
+ 'profile' => [$webpack.'js/profile.js'],
+ 'settings' => [$webpack.'js/settings.js'],
+ 'support' => [$webpack.'js/support.js'],
+ 'user_admin' => [$webpack.'js/user-admin.js'],
+ 'widget_admin' => [$webpack.'js/widget-admin.js'],
+ 'materia' => [$webpack.'js/materia.js'],
+ 'homepage' => [$webpack.'js/homepage.js'],
+ 'catalog' => [$webpack.'js/catalog.js'],
+ 'my_widgets' => [$webpack.'js/my-widgets.js'],
+ 'detail' => [$webpack.'js/detail.js'],
+ 'playpage' => [$webpack.'js/player-page.js'],
+ 'createpage' => [$webpack.'js/creator-page.js'],
+ 'scores' => [$webpack.'js/scores.js'],
+ 'guides' => [$webpack.'js/guides.js'],
+ 'retired' => [$webpack.'js/retired.js'],
+ 'no_attempts'=> [$webpack.'js/no-attempts.js'],
+ 'draft_not_playable' => [$webpack.'js/draft-not-playable.js'],
+ 'no_permission' => [$webpack.'js/no-permission.js'],
+ 'closed' => [$webpack.'js/closed.js'],
+ 'embedded_only' => [$webpack.'js/embedded-only.js'],
+ 'pre_embed' => [$webpack.'js/pre-embed-placeholder.js'],
+ 'help' => [$webpack.'js/help.js'],
+ '404' => [$webpack.'js/404.js'],
+ '500' => [$webpack.'js/500.js'],
+ 'media' => [$webpack.'js/media.js'],
+ 'qset_history' => [$webpack.'js/qset-history.js'],
+ 'post_login' => [$webpack.'js/lti-post-login.js'],
+ 'select_item' => [$webpack.'js/lti-select-item.js'],
+ 'open_preview' => [$webpack.'js/lti-open-preview.js'],
+ 'error_general' => [$webpack.'js/lti-error.js'],
+ 'react' => [
+ '//unpkg.com/react@16.13.1/umd/react.development.js',
+ '//unpkg.com/react-dom@16.13.1/umd/react-dom.development.js',
+ $webpack.'js/include.js'
],
-
- 'my_widgets' => [
- $cdnjs.'jqueryui/1.12.1/jquery-ui.min.js'
- ]
- ],
+ 'question-importer' => [$webpack.'js/question-importer.js']
+ ]
];
diff --git a/fuel/app/config/materia.php b/fuel/app/config/materia.php
index ed7fd531c..0d8204309 100644
--- a/fuel/app/config/materia.php
+++ b/fuel/app/config/materia.php
@@ -16,7 +16,9 @@
/*
* URLS throughout the system
- *
+ * \Uri::create('') will create full urls
+ * If you're having issues with urls not being correct
+ * You may wish to simply hard code these values
*/
'urls' => [
'root' => \Uri::create(''), // root directory http:://siteurl.com/
@@ -27,6 +29,13 @@
'preview' => \Uri::create('preview/'), // game preview urls http://siteurl.com/preview/3443
'static' => $_ENV['URLS_STATIC'] ?? \Uri::create(), // allows you to host another domain for static assets http://static.siteurl.com/
'engines' => $_ENV['URLS_ENGINES'] ?? \Uri::create('widget/'), // widget file locations
+ // where are js and css assets hosted?
+ // DEFAULT: public/dist (hosted as as https://site.com/)
+ 'js_css' => \Uri::create('/'),
+ // CDN PASS-THROUGH: set up aws cloudfront cdn have it load data from the default url
+ //'js_css' => '//xxxxxxxx.cloudfront.net/dist/',
+ // CDN UNPKG.COM: load assets from npm module with the same release (version must match your version of materia)
+ // 'js_css' => '//unpkg.com/materia-server-client-assets@2.2.0/',
],
@@ -48,7 +57,8 @@
// location of the lang files
'lang_path' => [
- 'login' => APPPATH.DS
+ 'login' => APPPATH.DS,
+ 'support' => APPPATH.DS
],
'default_users' => [
diff --git a/fuel/app/config/routes.php b/fuel/app/config/routes.php
index 0c66e8a3b..35e9f41ba 100644
--- a/fuel/app/config/routes.php
+++ b/fuel/app/config/routes.php
@@ -11,7 +11,7 @@
'_404_' => 'site/404',
'_500_' => 'site/500',
'_root_' => 'site/index', // Homepage
- 'permission-denied' => ['site/permission_denied', 'name' => 'nopermission'],
+ 'permission-denied' => ['site/permission_denied', 'name' => 'no_permission'],
'crossdomain' => 'site/crossdomain',
'help' => ['site/help', 'name' => 'help'], // The main docs page
@@ -28,8 +28,6 @@
'widgets/all' => 'widgets/all', // catalog page, with optional display option(s)
'widgets' => ['widgets/index', 'name' => 'catalog'], // catalog of all the widget engines
'my-widgets' => 'widgets/mywidgets/',
-
- 'edit/(:alnum)(/.*)?' => 'widgets/edit/$1',
'play/(:alnum)(/.*)?' => 'widgets/play_widget/$1',
'preview/(:alnum)(/.*)?' => 'widgets/preview_widget/$1',
'preview-embed/(:alnum)(/.*)?' => 'widgets/play_embedded_preview/$1',
diff --git a/fuel/app/lang/en/support.php b/fuel/app/lang/en/support.php
new file mode 100644
index 000000000..beeeec942
--- /dev/null
+++ b/fuel/app/lang/en/support.php
@@ -0,0 +1,11 @@
+ [
+ // 'helpdesk' => [
+ // 'title' => 'Trouble Logging In?',
+ // 'subtitle' => 'Contact the Service Desk.',
+ // 'website' => 'service desk support website dot edu',
+ // ]
+ // ]
+];
\ No newline at end of file
diff --git a/fuel/app/migrations/052_add_support_user_role.php b/fuel/app/migrations/052_add_support_user_role.php
new file mode 100644
index 000000000..4bf353373
--- /dev/null
+++ b/fuel/app/migrations/052_add_support_user_role.php
@@ -0,0 +1,21 @@
+where('name', 'support_user')
+ ->execute();
+ }
+}
diff --git a/fuel/app/migrations/053_hide_asset_option.php b/fuel/app/migrations/053_hide_asset_option.php
new file mode 100644
index 000000000..ce51c68e8
--- /dev/null
+++ b/fuel/app/migrations/053_hide_asset_option.php
@@ -0,0 +1,25 @@
+ ['constraint' => 11, 'type' => 'int', 'default' => -1],
+ 'is_deleted' => ['type' => 'enum', 'constraint' => "'0','1'", 'default' => '0'],
+ ]
+ );
+ }
+
+ public function down()
+ {
+ \DBUtil::drop_fields(
+ 'asset',
+ ['deleted_at', 'is_deleted']
+ );
+ }
+}
\ No newline at end of file
diff --git a/fuel/app/migrations/054_add_support_user_role_perms.php b/fuel/app/migrations/054_add_support_user_role_perms.php
new file mode 100644
index 000000000..00c53f42b
--- /dev/null
+++ b/fuel/app/migrations/054_add_support_user_role_perms.php
@@ -0,0 +1,50 @@
+from('perm_role_to_perm')
+ ->where('role_id', $support_role_id)
+ ->where('perm', \Materia\Perm::FULL)
+ ->execute();
+
+ // if not, grant support_user Perm::FULL
+ if ($pre_q->count() == 0) {
+ $q = \DB::query('INSERT INTO `perm_role_to_perm` (`role_id`, `perm`) values (:role_id, :perm) ON DUPLICATE KEY UPDATE `role_id` = :role_id, `perm` = :perm');
+ $q->param('role_id', $support_role_id);
+ $q->param('perm', \Materia\Perm::FULL);
+ $q->execute();
+ }
+
+ // now check to see if support_user already has Perm::SUPPORTUSER
+ $pre_q = \DB::select('role_id','perm')
+ ->from('perm_role_to_perm')
+ ->where('role_id', $support_role_id)
+ ->where('perm', \Materia\Perm::SUPPORTUSER)
+ ->execute();
+
+ // if not, grant support_user Perm::SUPPORTUSER
+ if ($pre_q->count() == 0) {
+ $q = \DB::query('INSERT INTO `perm_role_to_perm` (`role_id`, `perm`) values (:role_id, :perm) ON DUPLICATE KEY UPDATE `role_id` = :role_id, `perm` = :perm');
+ $q->param('role_id', $support_role_id);
+ $q->param('perm', \Materia\Perm::SUPPORTUSER);
+ $q->execute();
+ }
+ }
+
+ public function down()
+ {
+ $support_role_id = \Materia\Perm_Manager::get_role_id('support_user');
+
+ \DB::delete('perm_role_to_perm')
+ ->where('role_id', 'like', $support_role_id)
+ ->execute();
+ }
+}
diff --git a/fuel/app/migrations/055_update_super_user_role_permissions.php b/fuel/app/migrations/055_update_super_user_role_permissions.php
new file mode 100644
index 000000000..a9b95e0f9
--- /dev/null
+++ b/fuel/app/migrations/055_update_super_user_role_permissions.php
@@ -0,0 +1,28 @@
+param('role_id', $super_user_role_id)
+ ->param('old_perm', \Materia\Perm::BASICAUTHOR) // the OLD permission level (80)
+ ->param('new_perm', \Materia\Perm::SUPERUSER) // the NEW permission level (90)
+ ->execute();
+ }
+
+ public function down()
+ {
+ $super_user_role_id = \Materia\Perm_Manager::get_role_id('super_user');
+
+ \DB::QUERY('UPDATE `perm_role_to_perm` SET `perm` = :old_perm WHERE `role_id` = :role_id AND `perm` = :new_perm')
+ ->param('role_id', $super_user_role_id)
+ ->param('old_perm', \Materia\Perm::BASICAUTHOR)
+ ->param('new_perm', \Materia\Perm::SUPERUSER)
+ ->execute();
+ }
+}
diff --git a/fuel/app/modules/lti/classes/controller/error.php b/fuel/app/modules/lti/classes/controller/error.php
index 5511dc85e..b86f93acb 100644
--- a/fuel/app/modules/lti/classes/controller/error.php
+++ b/fuel/app/modules/lti/classes/controller/error.php
@@ -8,47 +8,55 @@
class Controller_Error extends \Controller
{
- use \Trait_Analytics;
- protected $_content_partial = 'partials/error_general';
- protected $_message = 'There was a problem';
+ use \Trait_CommonControllerTemplate;
+ use \Trait_Supportinfo;
- public function after($response)
+ // overrides Trait_CommonControllerTemplate->before()
+ public function before()
{
- $msg = str_replace('_', ' ', \Input::param('message', $this->_message));
- $system = str_replace('_', ' ', \Input::param('system', 'the system'));
-
$this->theme = \Theme::instance();
- $this->theme->set_template('layouts/main');
- $this->theme->set_partial('header', 'partials/header_empty');
- $this->theme->get_template()
- ->set('title', 'Error - '.$msg)
- ->set('page_type', 'lti-error');
-
- $this->theme->set_partial('content', $this->_content_partial )
- ->set('title', "Error - {$msg}")
- ->set('system', $system);
+ }
- $this->insert_analytics();
+ protected $_message = 'There was a problem';
+ protected $_type = 'error_general';
- \Js::push_group(['angular', 'materia']);
+ public function after($response)
+ {
\Js::push_inline('var BASE_URL = "'.\Uri::base().'";');
+ \Js::push_inline('var TITLE = "'.'Error - '.$this->_message.'";');
+ \Js::push_inline('var ERROR_TYPE = "'.$this->_type.'";');
\Js::push_inline('var STATIC_CROSSDOMAIN = "'.\Config::get('materia.urls.static').'";');
+ $this->add_inline_info();
+
\Css::push_group('lti');
+ $this->theme->set_template('layouts/react');
+ $this->theme->get_template()
+ ->set('title', 'Error - '.$this->_message)
+ ->set('page_type', 'lti-error');
+
+ \Js::push_group(['react', 'error_general']);
+
return \Response::forge(\Theme::instance()->render());
}
public function action_unknown_user()
{
- $this->_content_partial = 'partials/error_unknown_user';
+ $this->_type = 'error_unknown_user';
$this->_message = 'Unknown User';
}
public function action_unknown_assignment()
{
- $this->_content_partial = 'partials/error_unknown_assignment';
$this->_message = 'Unknown Assignment';
+ $this->_type = 'error_unknown_assignment';
+ }
+
+ public function action_invalid_oauth_request()
+ {
+ $this->_message = 'Invalid OAuth Request';
+ $this->_type = 'error_invalid_oauth_request';
}
/**
@@ -58,14 +66,14 @@ public function action_unknown_assignment()
*/
public function action_autoplay_misconfigured()
{
- $this->_content_partial = 'partials/error_autoplay_misconfigured';
$this->_message = 'Widget Misconfigured - Autoplay cannot be set to false for LTI assignment widgets';
+ $this->_type = 'error_autoplay_misconfigured';
}
public function action_guest_mode()
{
- $this->_content_partial = 'partials/error_lti_guest_mode';
$this->_message = 'Assignment has guest mode enabled';
+ $this->_type = 'error_lti_guest_mode';
}
public function action_index()
diff --git a/fuel/app/modules/lti/classes/controller/lti.php b/fuel/app/modules/lti/classes/controller/lti.php
index 177884c83..f14ac9f6d 100644
--- a/fuel/app/modules/lti/classes/controller/lti.php
+++ b/fuel/app/modules/lti/classes/controller/lti.php
@@ -8,8 +8,9 @@
class Controller_Lti extends \Controller
{
- use \Trait_Analytics;
+ use \Trait_CommonControllerTemplate;
+ // overrides Trait_CommonControllerTemplate->before()
public function before()
{
$this->theme = \Theme::instance();
@@ -46,22 +47,18 @@ public function action_index()
*/
public function action_login()
{
- if ( ! Oauth::validate_post()) \Response::redirect('/lti/error?message=invalid_oauth_request');
+ if ( ! Oauth::validate_post()) \Response::redirect('/lti/error/invalid_oauth_request');
$launch = LtiLaunch::from_request();
- if ( ! LtiUserManager::authenticate($launch)) \Response::redirect('/lti/error?message=invalid_oauth_request');
+ if ( ! LtiUserManager::authenticate($launch)) \Response::redirect('/lti/error/invalid_oauth_request');
- $this->theme->set_template('layouts/main')
+ $this->theme->set_template('layouts/react');
+ $this->theme->get_template()
->set('title', 'Materia')
->set('page_type', 'lti-login');
- $this->theme->set_partial('content', 'partials/post_login');
- $this->insert_analytics();
-
- \Js::push_inline('var BASE_URL = "'.\Uri::base().'";');
- \Js::push_inline('var STATIC_CROSSDOMAIN = "'.\Config::get('materia.urls.static').'";');
-
- \Css::push_group('core');
+ \Js::push_group(['react', 'post_login']);
+ \Css::push_group(['lti']);
return \Response::forge($this->theme->render());
}
@@ -72,7 +69,7 @@ public function action_login()
*/
public function action_picker(bool $authenticate = true)
{
- if ( ! Oauth::validate_post()) \Response::redirect('/lti/error?message=invalid_oauth_request');
+ if ( ! Oauth::validate_post()) \Response::redirect('/lti/error/invalid_oauth_request');
$launch = LtiLaunch::from_request();
if ($authenticate && ! LtiUserManager::authenticate($launch)) return \Response::redirect('/lti/error/unknown_user');
@@ -83,28 +80,23 @@ public function action_picker(bool $authenticate = true)
\Materia\Log::profile(['action_picker', \Input::post('selection_directive'), $system, $is_selector_mode ? 'yes' : 'no', $return_url], 'lti');
- $this->theme->set_template('layouts/main');
-
- \Js::push_group(['angular', 'materia', 'author']);
\Js::push_inline('var BASE_URL = "'.\Uri::base().'";');
\Js::push_inline('var WIDGET_URL = "'.\Config::get('materia.urls.engines').'";');
\Js::push_inline('var STATIC_CROSSDOMAIN = "'.\Config::get('materia.urls.static').'";');
- \Js::push_inline($this->theme->view('partials/select_item_js')
- ->set('system', $system));
- \Css::push_group(['core', 'lti']);
+ \Js::push_inline('var SYSTEM = "'.$system.'";');
+ \Css::push_group(['lti']);
if ($is_selector_mode && ! empty($return_url))
{
\Js::push_inline('var RETURN_URL = "'.$return_url.'"');
}
+ $this->theme->set_template('layouts/react');
$this->theme->get_template()
->set('title', 'Select a Widget for Use in '.$system)
->set('page_type', 'lti-select');
- $this->theme->set_partial('content', 'partials/select_item');
- $this->theme->set_partial('header', 'partials/header_empty');
- $this->insert_analytics();
+ \Js::push_group(['react', 'select_item']);
return \Response::forge($this->theme->render());
}
@@ -116,28 +108,33 @@ public function action_success(string $inst_id)
// If the current user does not have ownership over the embedded widget, find all of the users who do
$current_user_owns = \Materia\Perm_Manager::user_has_any_perm_to(\Model_User::find_current_id(), $inst_id, \Materia\Perm::INSTANCE, [\Materia\Perm::VISIBLE, \Materia\Perm::FULL]);
- $instance_owner_list = $current_user_owns ? [] : $inst->get_owners();
-
- $this->theme->set_template('layouts/main')
- ->set('title', 'Widget Connected Successfully')
- ->set('page_type', 'preview');
-
- $this->theme->set_partial('content', 'partials/open_preview')
- ->set('inst_name', $inst->name)
- ->set('widget_name', $inst->widget->name)
- ->set('preview_url', \Uri::create('/preview/'.$inst_id))
- ->set('icon', \Config::get('materia.urls.engines')."{$inst->widget->dir}img/icon-92.png")
- ->set('preview_embed_url', \Uri::create('/preview-embed/'.$inst_id))
- ->set('current_user_owns', $current_user_owns)
- ->set('instance_owner_list', $instance_owner_list);
- $this->insert_analytics();
+ $instance_owner_list = $current_user_owns ? [] : (array_map(function ($object)
+ {
+ return (object) [
+ 'first' => $object->first,
+ 'last' => $object->last,
+ 'id' => $object->id
+ ];
+ }, $inst->get_owners()));
\Js::push_inline('var BASE_URL = "'.\Uri::base().'";');
- \Js::push_inline('var inst_id = "'.$inst_id.'";');
+ \Js::push_inline('var PREVIEW_URL = "'.\Uri::create('/preview/'.$inst_id).'";');
+ \Js::push_inline('var ICON_URL = "'.\Config::get('materia.urls.engines')."{$inst->widget->dir}img/icon-92.png".'";');
+ \Js::push_inline('var PREVIEW_EMBED_URL = "'.\Uri::create('/preview-embed/'.$inst_id).'";');
+ \Js::push_inline('var CURRENT_USER_OWNS = "'.$current_user_owns.'";');
\Js::push_inline('var STATIC_CROSSDOMAIN = "'.\Config::get('materia.urls.static').'";');
+ \Js::push_inline('var OWNER_LIST = '.json_encode($instance_owner_list).';');
+ \Js::push_inline('var USER_ID = "'.\Model_User::find_current_id().'";');
+
+ \Css::push_group(['lti']);
+
+ $this->theme->set_template('layouts/react');
+ $this->theme->get_template()
+ ->set('title', 'Widget Connected Successfully')
+ ->set('page_type', 'preview');
- \Css::push_group(['core', 'lti']);
+ \Js::push_group(['react', 'open_preview']);
return \Response::forge($this->theme->render());
}
diff --git a/fuel/app/modules/lti/classes/controller/test.php b/fuel/app/modules/lti/classes/controller/test.php
index 24e977e30..5e9a58e42 100644
--- a/fuel/app/modules/lti/classes/controller/test.php
+++ b/fuel/app/modules/lti/classes/controller/test.php
@@ -16,7 +16,6 @@ public function before()
trace('these tests are not availible in production mode');
throw new \HttpNotFoundException;
}
- \Js::push_group('jquery');
parent::before();
}
diff --git a/fuel/app/modules/lti/classes/ltievents.php b/fuel/app/modules/lti/classes/ltievents.php
index 7c9ca6ff0..217c2c63a 100644
--- a/fuel/app/modules/lti/classes/ltievents.php
+++ b/fuel/app/modules/lti/classes/ltievents.php
@@ -23,7 +23,7 @@ public static function on_before_single_score_review()
$launch = LtiLaunch::from_request();
if ($launch)
{
- if ( ! Oauth::validate_post()) $result['redirect'] = '/lti/error?message=invalid_oauth_request';
+ if ( ! Oauth::validate_post()) $result['redirect'] = '/lti/error/invalid_oauth_request';
elseif ( ! LtiUserManager::authenticate($launch)) $result['redirect'] = '/lti/error/unknown_user';
$result['is_embedded'] = true;
}
@@ -61,7 +61,7 @@ public static function on_before_play_start_event($payload)
}
}
- if ( ! Oauth::validate_post()) $redirect = '/lti/error?message=invalid_oauth_request';
+ if ( ! Oauth::validate_post()) $redirect = '/lti/error/invalid_oauth_request';
elseif ( ! LtiUserManager::authenticate($launch)) $redirect = '/lti/error/unknown_user';
elseif ( ! $inst_id || ! $inst) $redirect = '/lti/error/unknown_assignment';
elseif ($inst->guest_access) $redirect = '/lti/error/guest_mode';
diff --git a/fuel/app/modules/lti/classes/ltilaunch.php b/fuel/app/modules/lti/classes/ltilaunch.php
index 6a11d9484..e963710fa 100644
--- a/fuel/app/modules/lti/classes/ltilaunch.php
+++ b/fuel/app/modules/lti/classes/ltilaunch.php
@@ -36,7 +36,7 @@ public static function from_request()
'last' => Utils::safeTrim(\Input::param('lis_person_name_family', '')),
'first' => Utils::safeTrim(\Input::param('lis_person_name_given', '')),
'fullname' => Utils::safeTrim(\Input::param('lis_person_name_full', '')),
- 'outcome_ext' => Utils::safeTrim(\Input::param('ext_outcome_data_values_accepted'), ''),
+ 'outcome_ext' => Utils::safeTrim(\Input::param('ext_outcome_data_values_accepted', '')),
'roles' => $roles,
'remote_id' => Utils::safeTrim(\Input::param($remote_id_field)),
'username' => Utils::safeTrim(\Input::param($remote_user_field)),
diff --git a/fuel/app/modules/lti/tests/ltievents.php b/fuel/app/modules/lti/tests/ltievents.php
index d7fe8218d..2de8d8dfb 100644
--- a/fuel/app/modules/lti/tests/ltievents.php
+++ b/fuel/app/modules/lti/tests/ltievents.php
@@ -29,7 +29,7 @@ public function test_on_before_play_start_event_shows_error_for_bad_oauth_reques
$this->assertArrayHasKey('redirect', $result);
$this->assertCount(1, $result);
- $this->assertEquals('/lti/error?message=invalid_oauth_request', $result['redirect']);
+ $this->assertEquals('/lti/error/invalid_oauth_request', $result['redirect']);
}
public function test_on_before_play_start_event_throws_unknown_user_exception_for_bad_user()
diff --git a/fuel/app/tasks/admin.php b/fuel/app/tasks/admin.php
index b3c8da572..628ba82ca 100644
--- a/fuel/app/tasks/admin.php
+++ b/fuel/app/tasks/admin.php
@@ -293,6 +293,7 @@ public static function populate_roles()
if (\Materia\Perm_Manager::create_role('no_author')) $roles++;
if (\Materia\Perm_Manager::create_role('basic_author')) $roles++;
if (\Materia\Perm_Manager::create_role('super_user')) $roles++;
+ if (\Materia\Perm_Manager::create_role('support_user')) $roles++;
if ($admin_role_id = \Materia\Perm_Manager::get_role_id('super_user'))
{
@@ -302,7 +303,20 @@ public static function populate_roles()
$q->execute();
$q->param('role_id', $admin_role_id);
- $q->param('perm', \Materia\Perm::AUTHORACCESS);
+ $q->param('perm', \Materia\Perm::SUPERUSER);
+ $q->execute();
+ }
+
+ if ($support_role_id = \Materia\Perm_Manager::get_role_id('support_user'))
+ {
+ $q = \DB::query('INSERT INTO `perm_role_to_perm` SET `role_id` = :role_id, `perm` = :perm ON DUPLICATE KEY UPDATE `perm` = :perm');
+
+ $q->param('role_id', $support_role_id);
+ $q->param('perm', \Materia\Perm::FULL);
+ $q->execute();
+
+ $q->param('role_id', $support_role_id);
+ $q->param('perm', \Materia\Perm::SUPPORTUSER);
$q->execute();
}
diff --git a/fuel/app/tests/api/v1.php b/fuel/app/tests/api/v1.php
index 577b97529..42b524c6d 100644
--- a/fuel/app/tests/api/v1.php
+++ b/fuel/app/tests/api/v1.php
@@ -15,7 +15,7 @@ public function test_allPublicAPIMethodsHaveTests()
$testMethods = get_class_methods($this);
foreach ($apiMethods as $value)
{
- $this->assertContains('test_'.$value, $testMethods);
+ $this->assertContainsEquals('test_'.$value, $testMethods);
}
}
@@ -105,11 +105,47 @@ public function test_widget_instance_access_perms_verify()
public function test_widget_instances_get()
{
+ // Create widget instance
+ $this->_as_author();
+ $title = "My Test Widget";
+ $question = 'What rhymes with harvest fests but are half as exciting (or tasty)';
+ $answer = 'Tests';
+ $qset = $this->create_new_qset($question, $answer);
+ $widget = $this->make_disposable_widget();
+
+ $instance = Api_V1::widget_instance_new($widget->id, $title, $qset, true);
+
// ======= AS NO ONE ========
+ $this->_as_noauth();
+
+ // ----- returns empty array if not requesting a specific instance --------
$output = Api_V1::widget_instances_get();
$this->assertIsArray($output);
$this->assertCount(0, $output);
+ // ----- loads specific instance without qset --------
+ $output = Api_V1::widget_instances_get($instance->id);
+ $this->assertIsArray($output);
+ $this->assertCount(1, $output);
+ foreach ($output as $key => $value)
+ {
+ $this->assert_is_widget_instance($value, true);
+ $this->assertObjectHasAttribute('qset', $value);
+ $this->assertNull($value->qset->data);
+ $this->assertNull($value->qset->version);
+ }
+
+ // ----- loads specific instance with qset --------
+ $output = Api_V1::widget_instances_get($instance->id, false, true);
+ $this->assertIsArray($output);
+ $this->assertCount(1, $output);
+ foreach ($output as $key => $value)
+ {
+ $this->assert_is_widget_instance($value, true);
+ $this->assertObjectHasAttribute('qset', $value);
+ $this->assert_is_qset($value->qset);
+ }
+
// ======= STUDENT ========
$this->_as_student();
$output = Api_V1::widget_instances_get();
@@ -144,6 +180,11 @@ public function test_widget_instances_get()
}
+ public function test_widget_paginate_instances_get()
+ {
+
+ }
+
public function test_widget_instance_new()
{
$widget = $this->make_disposable_widget();
@@ -486,7 +527,7 @@ public function test_widget_instance_lock_for_another_user()
// the lock is stored in a cache that expires
// let's manually clear cache now, effectively removing the lock
- \Cache::delete_all('');
+ \Cache::delete('instance-lock.'.($inst->id));
$this->assertTrue(Api_V1::widget_instance_lock($inst->id)); // lock should be expired, i can edit it
}
@@ -516,9 +557,9 @@ public function test_widget_instance_copy()
$output = Api_V1::widget_instance_copy($inst_id, 'Copied Widget');
- $this->assert_is_valid_id($output);
+ $this->assert_is_valid_id($output->id);
- $insts = Api_V1::widget_instances_get($output);
+ $insts = Api_V1::widget_instances_get($output->id);
$this->assert_is_widget_instance($insts[0], true);
$this->assertEquals('Copied Widget', $insts[0]->name);
$this->assertEquals(true, $insts[0]->is_draft);
@@ -536,9 +577,9 @@ public function test_widget_instance_copy()
$output = Api_V1::widget_instance_copy($inst_id, 'Copied Widget');
- $this->assert_is_valid_id($output);
+ $this->assert_is_valid_id($output->id);
- $insts = Api_V1::widget_instances_get($output);
+ $insts = Api_V1::widget_instances_get($output->id);
$this->assert_is_widget_instance($insts[0], true);
$this->assertEquals('Copied Widget', $insts[0]->name);
$this->assertEquals(true, $insts[0]->is_draft);
@@ -983,6 +1024,10 @@ public function test_play_logs_get()
}
+ public function test_paginated_play_logs_get()
+ {
+ }
+
public function test_score_summary_get()
{
// ======= AS NO ONE ========
@@ -1390,12 +1435,12 @@ public function test_notification_delete(){
$id = $widget->id;
// ======= AS NO ONE ========
- $output = Api_V1::notification_delete(5);
+ $output = Api_V1::notification_delete(5, false);
$this->assert_invalid_login_message($output);
// ======= STUDENT ========
$this->_as_student();
- $output = Api_V1::notification_delete(5);
+ $output = Api_V1::notification_delete(5, false);
$this->assertFalse($output);
$author = $this->_as_author();
@@ -1422,17 +1467,24 @@ public function test_notification_delete(){
// try as someone author2
$this->_as_author_2();
- $output = Api_V1::notification_delete($notifications[0]['id']);
+ $output = Api_V1::notification_delete($notifications[0]['id'], false);
$this->assertFalse($output);
$this->_as_author();
- $output = Api_V1::notification_delete($notifications[0]['id']);
+ $output = Api_V1::notification_delete($notifications[0]['id'], false);
$this->assertTrue($output);
$this->_as_author();
$notifications = Api_V1::notifications_get();
$this->assertEquals($start_count, count($notifications));
+ // try deleting all
+ $this->_as_author();
+ $output = Api_V1::notification_delete(null, true);
+ $this->assertTrue($output);
+
+ $notifications = Api_V1::notifications_get();
+ $this->assertEquals(0, count($notifications));
}
public function test_semester_get()
@@ -1513,7 +1565,7 @@ protected function assert_is_semester_rage($semester)
$this->assertArrayHasKey('year', $semester);
$this->assertGreaterThan(0, $semester['year']);
$this->assertArrayHasKey('semester', $semester);
- $this->assertContains($semester['semester'], array('Spring', 'Summer', 'Fall') );
+ $this->assertContainsEquals($semester['semester'], array('Spring', 'Summer', 'Fall') );
$this->assertArrayHasKey('start', $semester);
$this->assertGreaterThan(0, $semester['start']);
$this->assertArrayHasKey('end', $semester);
diff --git a/fuel/app/tests/classes/materia/perm/manager.php b/fuel/app/tests/classes/materia/perm/manager.php
index 9e00438c5..1049ada2b 100644
--- a/fuel/app/tests/classes/materia/perm/manager.php
+++ b/fuel/app/tests/classes/materia/perm/manager.php
@@ -66,14 +66,14 @@ public function test_get_user_ids_with_role()
$newSuperUser = $this->make_random_super_user();
$superUserIds = Perm_Manager::get_user_ids_with_role('super_user');
- $this->assertContains((int)$newSuperUser->id, $superUserIds);
+ $this->assertContainsEquals($newSuperUser->id, $superUserIds);
$newAuthorOne = $this->make_random_author();
$newAuthorTwo = $this->make_random_author();
$studentIds = Perm_Manager::get_user_ids_with_role('basic_author');
- $this->assertContains((int)$newAuthorOne->id, $studentIds);
- $this->assertContains((int)$newAuthorTwo->id, $studentIds);
+ $this->assertContainsEquals($newAuthorOne->id, $studentIds);
+ $this->assertContainsEquals($newAuthorTwo->id, $studentIds);
$this->assertCount(2, $studentIds);
}
diff --git a/fuel/app/tests/controller/api/instance.php b/fuel/app/tests/controller/api/instance.php
index e01422a79..2bcf7e8b3 100644
--- a/fuel/app/tests/controller/api/instance.php
+++ b/fuel/app/tests/controller/api/instance.php
@@ -57,4 +57,89 @@ public function test_get_history()
$this->assertTrue(is_array($output));
$this->assertCount(1, $output);
}
+
+ public function test_post_request_access()
+ {
+ $_SERVER['HTTP_ACCEPT'] = 'application/json';
+
+ // ======= NO INST ID PROVIDED ========
+ $response = Request::forge('/api/instance/request_access')
+ ->set_method('POST')
+ ->execute()
+ ->response();
+
+ $this->assertEquals($response->status, 401);
+ $this->assertEquals($response->body, '"Requires an inst_id parameter"');
+
+ // ======= NO OWNER ID PROVIDED ========
+ $response = Request::forge('/api/instance/request_access')
+ ->set_method('POST')
+ ->set_json('inst_id', 555)
+ ->execute()
+ ->response();
+
+ $this->assertEquals($response->status, 401);
+ $this->assertEquals($response->body, '"Requires an owner_id parameter"');
+
+ // == Now we're an author
+ $this->_as_author();
+
+ // == Make a widget instance
+ $widget = $this->make_disposable_widget();
+ $title = "My Test Widget";
+ $question = 'This is another word for test';
+ $answer = 'Assert';
+ $qset = $this->create_new_qset($question, $answer);
+ $instance = Api_V1::widget_instance_new($widget->id, $title, $qset, false);
+ $author_id = \Model_User::find_current_id();
+
+ // ======= NO INST ID FOUND ========
+ $response = Request::forge('/api/instance/request_access')
+ ->set_method('POST')
+ ->set_json('inst_id', 555)
+ ->set_json('owner_id', $author_id)
+ ->execute()
+ ->response();
+
+ $this->assertEquals($response->body, '"Instance not found"');
+ $this->assertEquals($response->status, 404);
+
+ // ======= NO OWNER ID FOUND ========
+ $response = Request::forge('/api/instance/request_access')
+ ->set_method('POST')
+ ->set_json('inst_id', $instance->id)
+ ->set_json('owner_id', 111)
+ ->execute()
+ ->response();
+
+ $this->assertEquals($response->status, 404);
+ $this->assertEquals($response->body, '"Owner not found"');
+
+ // ======= OWNER DOES NOT OWN INSTANCE =========
+ // Switch users
+ $this->_as_student();
+
+ $response = Request::forge('/api/instance/request_access')
+ ->set_method('POST')
+ ->set_json('inst_id', $instance->id)
+ ->set_json('owner_id', \Model_User::find_current_id())
+ ->execute()
+ ->response();
+
+ $this->assertEquals($response->status, 404);
+ $this->assertEquals($response->body, '"Owner does not own instance"');
+
+ // ======= SUCCESSFUL REQUEST ========
+ $response = Request::forge('/api/instance/request_access')
+ ->set_method('POST')
+ ->set_json('inst_id', $instance->id)
+ ->set_json('owner_id', $author_id)
+ ->execute()
+ ->response();
+
+ // TODO: Test is_valid_hash
+
+ $this->assertEquals($response->body, 'true');
+ $this->assertEquals($response->status, 200);
+ }
}
\ No newline at end of file
diff --git a/fuel/app/tests/model/user.php b/fuel/app/tests/model/user.php
index 157fec2a3..4cd42d0c4 100644
--- a/fuel/app/tests/model/user.php
+++ b/fuel/app/tests/model/user.php
@@ -97,8 +97,8 @@ public function test_find_by_name_search_finds_multiple_matches()
$ids = [$x[0]->id, $x[1]->id];
- self::assertContains((int)$user1->id, $ids);
- self::assertContains((int)$user2->id, $ids);
+ self::assertContainsEquals($user1->id, $ids);
+ self::assertContainsEquals($user2->id, $ids);
}
}
diff --git a/fuel/app/themes/default/layouts/main.php b/fuel/app/themes/default/layouts/main.php
deleted file mode 100644
index cd97f08c3..000000000
--- a/fuel/app/themes/default/layouts/main.php
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-