I am using Moodle 2.7 and in the Quiz activity there is the overview page for all attempts of the learners.
The table is under mymoodle/mod/quiz/report.php?id=50&mode=overview
Right now only admin users or users with the capability 'mod/quiz:viewreports' can see the table.
How to add users, without using any capability, who will be able to see this report?
Right now every user, without the capability gets the error from report.php:
$reportlist = quiz_report_list($context);
if (empty($reportlist) !totara_is_manager($userid)) {
print_error('erroraccessingreport', 'quiz');
}
// Validate the requested report name.
if ($mode == '') {
// Default to first accessible report and redirect.
$url->param('mode', reset($reportlist));
redirect($url);
} else if (!in_array($mode, $reportlist)) {
print_error('erroraccessingreport', 'quiz');
}
if (!is_readable("report/$mode/report.php")) {
print_error('reportnotfound', 'quiz', '', $mode);
}
The table function is under reportlib.php:
function quiz_report_list($context) {
global $DB;
static $reportlist = null;
if (!is_null($reportlist)) {
return $reportlist;
}
$reports = $DB->get_records('quiz_reports', null, 'displayorder DESC', 'name, capability');
$reportdirs = core_component::get_plugin_list('quiz');
// Order the reports tab in descending order of displayorder.
$reportcaps = array();
foreach ($reports as $key => $report) {
if (array_key_exists($report->name, $reportdirs)) {
$reportcaps[$report->name] = $report->capability;
}
}
// Add any other reports, which are on disc but not in the DB, on the end.
foreach ($reportdirs as $reportname => $notused) {
if (!isset($reportcaps[$reportname])) {
$reportcaps[$reportname] = null;
}
}
$reportlist = array();
foreach ($reportcaps as $name => $capability) {
if (empty($capability)) {
$capability = 'mod/quiz:viewreports';
}
if (has_capability($capability, $context)) {
$reportlist[] = $name;
}
}
return $reportlist;
}
I want to add designated people by their id, who will act as managers.
If you want to completely bypass the capabilities' mechanism for viewing reports, then you could always comment the array values in access.php corresponding to the key 'mod/quiz:viewreports'. In other words, you can go to /mod/quiz/db/access.php and substitute
// View the quiz reports.
'mod/quiz:viewreports' => array(
'riskbitmask' => RISK_PERSONAL,
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
with
// View the quiz reports.
'mod/quiz:viewreports' => array(
// 'riskbitmask' => RISK_PERSONAL,
// 'captype' => 'read',
// 'contextlevel' => CONTEXT_MODULE,
// 'archetypes' => array(
// 'teacher' => CAP_ALLOW,
// 'editingteacher' => CAP_ALLOW,
// 'manager' => CAP_ALLOW
)
),
or, alternatively, you can tune or turn on the entries according to your necessities. For more information see:
https://docs.moodle.org/dev/Access_API
Then you can
check the ID of the current user ($USER->id) and
write some custom function to decide if this user can or cannot see the report.
Note: I would not bypass the capabilities mechanism, though, because it is reliable and safe. You could however tune it in order to allow only user groups defined by you.
Related
I had been slowly learning PHP OOP, I decided it was time to start inserting into my table, however it doesn't seem to be inserting. I compared my code with working versions and I can't see what the problem might be, I attempted a var_dump(), the query returned as I expected, I tested my database class by creating an new user, it was successfully created so I assume it isn't that, I tested the SQL query and it was able to insert, I'm at a loss for it might be now
form
<?php
session_start();
require ("classes/Review.php");
require ("classes/Database.php");
if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
$reviewing = new ReviewValidation();
$review = $reviewTitle = "";
$post = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING);
$reviewTitle = $post['reviewTitle'];
$review = $post['review'];
$errors = array();
$fields = array(
'title' => array(
'validate' => 'title',
'message' => 'Title must be at least fife characters',
'value' => $reviewTitle,
),
'review' => array(
'validate' => 'review',
'message' => 'Your review must be at least three hundred characters',
'value' => $review,
)
);
foreach($fields as $key => $value)
{
$validation_result = $reviewing->{$value['validate']}($value['value']);
if(!$validation_result)
{
$errors[] = ['name' => $key, 'error' => $value['message']];
}
}
if(empty($errors))
{
try
{
$db = new Database;
$success = ["message" => "Review subbbmitted"];
$process = $db->prepare('INSERT INTO reviews (reviewTitle)
VALUES
(:reviewTitle');
$process->bindValue(':reviewTitle', $reviewTitle);
$process->execute();
}
catch(Exception $e)
{
$errors[] = ['response' => 'fail'];
}
}
}
header('Content-Type: application/json');
if (empty($errors))
{
echo json_encode($success);
}
else
{
echo json_encode(["errors" => $errors]);
}
class
<?php
class ReviewValidation
{
private
$db,
$review,
$reviewTitle;
private static
$reviewLength = 50,
$rewviewtitleLength = 5;
public static function title($reviewTitle)
{
return(strlen($reviewTitle) >= self::$rewviewtitleLength);
}
public static function review($review)
{
return(strlen($review) >= self::$reviewLength);
}
}
Looks like you may be missing the closing ) in the insert query:
$process = $db->prepare('INSERT INTO reviews (reviewTitle)
VALUES
(:reviewTitle)');
If you add in a closing parenthesis after :reviewTitle, before the single quote your syntax will be correct (shown above).
I also noticed that your calls to the static methods in the ReviewValidation class are using the object operator (->). To access static methods you need to utilize the scope resolution operator.
So your $validation_result line should look like:
$validation_result = ReviewValidation::{$value['validate']}($value['value']);
I think because of this, the validation may have been passing, which is why you where getting the no default value issue.
What I'm using:
cakephp version 2.4.1
What I have:
table channel_settings with attributes (id, mask_id, provider_id, servicetype_id, channel_id, created, modified)
table channel_alert_defaults with attributes (id, provider_id, servicetype_id, channel_id)
In page add new channel_setting user can insert each provider to each servicetype to it's channel.
Now what I need is when user choose servicetype to --Any-- then besides this one record, there will be some multiple insert into database for some servicetype because some servicetype need different channel. Amount of multiple insert depent on how many a provider has servicetype setting and channel in table channel_alert_defaults
Here's existing system:
What I want now:
Here's what I'm trying, but I still don't get any idea how multiple insert code is
public function add() {
if ($this->request->is('post')) {
Controller::loadModel('Servicetype');
$this->Servicetype->recursive = -1;
$servicetype = $this->Servicetype->find('all');
$this->request->data['ChannelSetting']['mask_id'] = $this->Session->read('current_mask_id');
$maskid = $this->request->data['ChannelSetting']['mask_id'];
$providerid = $this->request->data['ChannelSetting']['provider_id'];
$servicetypeid = $this->request->data['ChannelSetting']['servicetype_id'];
$this->ChannelSetting->create();
if ($this->request->data['ChannelSetting']['servicetype_id'] == 0) {
Controller::loadModel('ChannelAlertDefaults');
$this->ChannelAlertDefault->recursive = -1;
$channelalertdefault = $this->ChannelAlertDefaults->findByProviderId($providerid);
// loop insert goes here, I think...
if ($this->ChannelSetting->save($this->request->data)) {
$this->Session->setFlash(__('The channel setting has been saved'), 'success');
return $this->redirect(array('action' => 'add'));
}
else {
$this->Session->setFlash(__('The channel setting failed to save'));
}
} else {
if ($this->ChannelSetting->save($this->request->data)) {
$this->Session->setFlash(__('The channel setting has been saved'), 'success');
return $this->redirect(array('action' => 'add'));
}
else {
$this->Session->setFlash(__('The channel setting failed to save'));
}
}
if ($this->ChannelSetting->save($this->request->data)) {
$this->Session->setFlash(__('The channel setting has been saved'), 'success');
return $this->redirect(array('action' => 'add'));
}
else {
$this->Session->setFlash(__('The channel setting failed to save'));
}
}
}
PS: why I want this? So that I don't have to insert data one by one for each provider. Thank you
I can't test now but maybe you can try something like this:
$data = array(
[0] => array(
'MASK' => $this->request->data['ChannelSetting']['mask_id'],
'PROVIDER' => $this->request->data['ChannelSetting']['provider_id'],
'SERVICE_TYPE' => *all*
),
[1] => array(
'MASK' => $this->request->data['ChannelSetting']['mask_id'],
'PROVIDER' => $this->request->data['ChannelSetting']['provider_id'],
'SERVICE_TYPE' => *otp*
),
[2] => array(
'MASK' => $this->request->data['ChannelSetting']['mask_id'],
'PROVIDER' => $this->request->data['ChannelSetting']['provider_id'],
'SERVICE_TYPE' => *httpalert*
)
);
$this->ChannelSetting->saveAll($data, array('deep' => true));
#Javi thank you for give me inspiration on how I should solve the problem.
What I've done:
1. count how many data from table channel_alert_defaults than correspondent to provider_id from user input
2. Make a loop insert into table channel_setting using saveAll
Here is my code:
Controller::loadModel('ChannelAlertDefault');
$this->ChannelAlertDefault->recursive = 1;
$channelalertdefault = $this->ChannelAlertDefault->findAllByProviderId($providerid);
$amount = count($channelalertdefault); // to count how many array
// Here is the loop...
if ($this->ChannelSetting->save($this->request->data)) {
for($i=0;$i<$amount;$i++) {
$this->request->data['ChannelSetting']['mask_id'] = $this->Session->read('current_mask_id');
$this->request->data['ChannelSetting']['provider_id'] = $channelalertdefault[$i]['ChannelAlertDefault']['provider_id'];
$this->request->data['ChannelSetting']['servicetype_id'] = $channelalertdefault[$i]['ChannelAlertDefault']['servicetype_id'];
$this->request->data['ChannelSetting']['channel_id'] = $channelalertdefault[$i]['ChannelAlertDefault']['channel_id'];
$this->ChannelSetting->saveAll($this->request->data);
}
I'm currently working on a project using the Phalcon Framework that has pages with complex forms and a lot of inputs, to break it down nicely I'm dividing the forms into a step-by-step process.
How would one validate the form on each step before going to the next step and then save the whole form on the final step?
I can't seem to find anything documented about this sort of process as it likes to validate the form in it's entirety if I use the form builder.
Simple, just create a custom methods in your form class to validate any step, and the posted data from some step save into message class and store it into session by "stepX", when posted data is not valid just set defaults from post. When valid save it into session as i describe above.
For example how i mean "controller"
<?php
class MyController extends BaseController {
public function processStep1Action(){
$form = new MyForm();
if($this->request->isPost()){//im using my custom request class
if(!$form->isValid($this->request->getPost()){
//error messages goes here
$form->setDefaultsFromRequest($this->request); // it will set the filled data
}
else {
$messageClass = new MyMessageContainer();
$messageClass->setData($this->request);//inside parse requested data into message class, or parse it as $messageClass->name = $this->request->getPost('name');
$this->session->save('step1',$messageClass); //maybe it would be want to serialize it
//then redirect to the step 2 or x
}
}
}
}
So in the next step you can access data from sessions $this->session->get('step1'); so you can in final step load all posted data and store it into DB.
I hope this helps! :)
here is my form maybe it can be helpful for you.
<?php
namespace Manager\Library\Forms\User;
use Phalcon\Forms\Form,
Phalcon\Forms\Element\Email,
Phalcon\Forms\Element\Select,
Phalcon\Forms\Element\Password,
Phalcon\Forms\Element\Check,
Phalcon\Validation\Validator\Confirmation,
Phalcon\Validation\Validator\StringLength,
Phalcon\Forms\Element\Submit,
Phalcon\Validation\Validator\PresenceOf,
Model\Group;
class AddUser extends Form {
public function initialize()
{
$email = new Email('email');
$email->addValidators(array(
new \Phalcon\Validation\Validator\Email(array(
'message' => 'Nezadali jste email nebo má nesprávny tvar(email#domena.tld).'
))
));
$this->add($email);
$this->initGroupElement();
$password = new Password('password');
$password
->addValidator(new StringLength(array('min' => 6,'messageMinimum' => 'Nezadali jste heslo nebo je příliš krátke, minimální počet znaků je 6.')))
->addValidator(new Confirmation(array('with' => 'password-again',"message" => "Zadané hesla se neshodují.")));
$this->add($password);
$repeatPassword = new Password('password-again');
$this->add($repeatPassword);
$this->initializeProfileElements();
$active = new Check('active',array('value' => 1));
$this->add($active);
$this->add( new Submit('save') );
\Phalcon\Tag::setDefault('password', '');
\Phalcon\Tag::setDefault('password-again', '');
}
public function initializeEdit(){
$email = new Email('email');
$email->addValidators(array(
new \Phalcon\Validation\Validator\Email(array(
'message' => 'Nezadali jste email nebo má nesprávny tvar(email#domena.tld).'
))
));
$this->add($email);
$this->initGroupElement();
$password = new Password('password');
$this->add($password);
$repeatPassword = new Password('password-again');
$this->add($repeatPassword);
$this->initializeProfileElements();
$active = new Check('active',array('value' => 1));
$this->add($active);
$this->add( new Submit('save') );
\Phalcon\Tag::setDefault('password', '');
\Phalcon\Tag::setDefault('password-again', '');
}
protected function initGroupElement(){
$auth = \Core\Auth::getIdentity();
$groups = new Group();
// $groups->addColumns(array('id','name'));
//set global condition about Super Admin
$groups->addFilter('id', 1,'<>');
if($auth){
//set restrictions for main groups
if((int)$auth->group_id === 1){ //super admingroup
//no filter
}
else if((int)$auth->group_id === 2){ //admin group
$groups->addFilter('id', 1,'>');
}
else if((int)$auth->group_id === 6){//Provozovatel group
$groups->addFilter('id',array(3,6,7));
$groups->addFilter('public', 1,'=',true);
}
else { // other groups
$groups->addFilter('public', 1);
}
}
$groups = $groups->findFiltered();
$groupElement = new Select('group');
foreach($groups as $group){
$groupElement->addOption(array($group->id => $group->name));
}
$this->add($groupElement);
}
protected function initializeProfileElements(){
$forename = new \Phalcon\Forms\Element\Text('forename');
$this->add($forename);
$surname = new \Phalcon\Forms\Element\Text('surname');
$this->add($surname);
$street = new \Phalcon\Forms\Element\Text('street');
$this->add($street);
$postal = new \Phalcon\Forms\Element\Text('postal');
$this->add($postal);
$city = new \Phalcon\Forms\Element\Text('city');
$this->add($city);
$ic = new \Phalcon\Forms\Element\Text('ic');
$this->add($ic);
$dic = new \Phalcon\Forms\Element\Text('dic');
$this->add($dic);
}
public function setDefault($fieldName,$value){
\Phalcon\Tag::setDefault($fieldName, $value);
}
public function setDefaults($object){
if($object instanceof \Model\User){
$this->setDefaultsFromObject($object);
}
else if($object instanceof \Phalcon\Http\Request){
$this->setDefaultsFromRequest($object);
}
}
protected function setDefaultsFromObject(\Model\User $user){
$profile = $user->getRelated('\Model\Profile');
\Phalcon\Tag::setDefaults(array(
'email' => $user->email,
'group' => $user->group_id,
'active' => $user->active,
'forename' => $profile->forename,
'surname' => $profile->surname,
'street' => $profile->street,
'city' => $profile->city,
'postal' => $profile->postal,
'ic' => $profile->IC,
'dic' => $profile->DIC
));
}
protected function setDefaultsFromRequest(\Phalcon\Http\Request $request){
\Phalcon\Tag::setDefaults(array(
'email' => $request->getPost('email'),
'group' => $request->getPost('group'),
'active' => $request->getPost('active')
));
\Phalcon\Tag::setDefaults(array(
'forename' => $request->getPost('forename'),
'surname' => $request->getPost('surname'),
'street' => $request->getPost('street'),
'city' => $request->getPost('city'),
'postal' => $request->getPost('postal'),
'ic' => $request->getPost('ic'),
'dic' => $request->getPost('dic')
));
}
}
In addition to Kamil's answer, another option to consider is to use Javascript on the front-end to handle your multi-step form. This will add some complexity as you will need to have the javascript to handle the form steps and do preliminary validation, but it only requires a single submit where you can validate content within a single method.
my app is a Book manager where I can create Books and Pages.
I have my bookController with a "store" on POST, which store a title and a description.
public function store()
{
$rules = array(
'title' => 'required|min:3',
'description' => 'required|min:30'
);
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Response::json(
array(
'metadata' => array(
'error' => true,
'message' => 'The book creation has failed'
)
),
400
);
}
else {
$slug = Str::slug(Request::get('title'));
$existSlug = Book::where('slug',$slug)->get();
if(count($existSlug) > 0) {
return Response::json(
array(
'metadata' => array(
'error' => true,
'message' => 'This title is already taken'
)
),
400
);
}
else {
$book = new Book;
$book->title = Request::get('title');
$book->slug = $slug;
$book->description = Request::get('description');
$book->user_id = Auth::user()->id;
$book->status = false;
$book->save();
$stored = $book->toArray();
$metadata = array(
'metadata' => array(
'error' => false,
)
);
return Response::json(
array_merge($stored,$metadata),
201
);
}
}
}
I also have a pageController with a "store" on POST, which store a page content :
public function store()
{
$rules = array(
'content' => 'required|between:300,350',
'book_id' => 'required|exists:books,id'
);
$validator = Validator::make(Input::all(), $rules);
if($validator->fails()) {
return Response::json(
array(
'metadata' => array(
'error' => true,
'message' => 'The page must be between 300 and 350 characters'
)
),
400
);
}
else {
$book = Book::find(Input::get('book_id'));
$content = Input::get('content');
$parent = Page::where('book_id',$book->id)->where('status',1)->orderBy('id', 'desc')->first();
if($parent){
$parent_id = $parent->id;
$parent_number = $parent->number;
$status = 0; //Define the status of the created page
}
else{
//If it's the first page of the book
$parent_id = 0;
$parent_number = 0;
$status = 1; //if there's no parent page, the new page is the first - auto validated - page of the book.
if($book->user_id != Auth::user()->id) {
return Response::json(
array(
'metadata' => array(
'error' => true,
'message' => 'You have to be the author of a book to write the first page.'
)
),
403
);
}
}
$page = new Page;
$page->content = $content;
$page->book_id = $book->id;
$page->parent_id = $parent_id;
$page->number = $parent_number + 1;
$page->user_id = Auth::user()->id;
$page->status = $status;
$page->save();
$stored = $page->toArray();
$metadata = array(
'metadata' => array(
'error' => false
)
);
return Response::json(
array_merge($stored,$metadata),
201
);
}
}
Whenever someone creates a book, he has to write at least its first page. This result in a form with an input title, description and content.
I send a POST to [...]/books with my input title and description
If Success => I get the book id, and send it with the input content to [...]/pages.
Here are my problems :
Someone can send a post on [...]/books and will store a new book with no page
I want to solve this in the more "restFUL way", meaning no "hackish solution" like sending the content to /books and make a page validation in the bookController
Also, even if I chose the hackish way, my API is still not safe : I can stop the second request (to /pages) to be sent.
How do I handle this co-dependency ?
1st
Your controllers are doing too much, they are not supposed to know anything about your business logic this is something that should be handle by specific classes (models, repositories, domain logic classes).
Create some classes to handle this logic, send the Input to them and make it happen. Call them whatever you need to, using Laravel is great because you can do whatever you want with your code.
2nd
If you have different data constraints to be enforced, you can:
Handle them on the same request
Depends on your interface, if you have everything you need on a single page, you just send the data and handle it on a repository, which has access to all your models.
An example that can be used for both could be:
A book repository using Dependency Injection, which means that Book and Page will be automatically instantiated by Laravel:
class BookRepository {
__construct(Book $book, Page $page)
{
$this->book = $book;
$this->page = $page;
}
public function store($input)
{
if ( ! $this->book->validate($input) || !$this->page->validate($input))
{
return 'error';
}
$book->create(input);
$page->create($input);
}
}
A Base Model with your validation:
class Book extends BaseModel {
public function validate($input)
{
/// validate here and return
}
}
Your models and rules for each:
class Book extends BaseModel {
$book_rules = array(
'title' => 'required|min:3',
'description' => 'required|min:30'
);
}
class Page extends BaseModel {
$page_rules = array(
'content' => 'required|between:300,350',
'book_id' => 'required|exists:books,id'
);
}
And then you create your view having book info and page info, and which will POST to BookController#store:
class BookController extends Controller {
public function __controller(BookRepository $book_repository)
{
$this->book_repository = $book_repository;
}
public function store()
{
if ( ! $this->book_repository->store($input))
{
return Redirect::back()
->withErrors(
$this->book_repository
->validation
->messages()
->all()
);
}
return Redirect::to('success');
}
}
Again we are using Dependency Injection. $book_repository will be instantiated automatically. So your Controller doesn't need to know what a Book or a Page do, it just need to get the request and pass to a repository that will take care of everything.
It's not all there, but it's a start.
Handle them on different requests
This is usual. User send a request, app check and store data. User send a second request, app check it all and send back errors, if needed.
Handle them in background
This is a smarter way to do it. Your app will receive all data, in one or more requests, store them, check them using a queue worker and send e-mails to the user telling him that there are some data to be filled. Books with no pages can be deleted after some time. You don't risk having bad data and your user will know what's missing as soon as you do too.
I have created a Facebook App that i need people to only enter their data to once.
It's all working and the database is being populated, but i need to make sure people don't keep coming back and re-entering their data endlessly.
What's the best way to check if the user has already submitted their data ?
The signed_request could still have been submitted and their data not entered so i need the check for both to work.
Ideally the PHP would just check for FB ID + other data, and only display a confirmation / thankyou page.
Currently my php to send to the database is:
class Users_Model extends CI_Model {
protected $_name = 'users';
function add($id,$key,$value) {
$data = array(
'id' => $id,
'name' => $key,
'value' => $value
);
return $this->db->insert($this->_name, $data);
}
function update($id,$key,$value) {
$data = array(
'value' => $value
);
$this->db->where(array(
'id' => $id,
'name' => $key
));
return $this->db->update($this->_name, $data);
}
function exists($id,$key=null) {
if($key == null) {
$this->db->where(array(
'id' => $id
));
} else {
$this->db->where(array(
'id' => $id,
'name' => $key
));
}
$query = $this->db->get($this->_name);
if($query->num_rows() > 0) {
return true;
}
return false;
}
function remove($id) {
$data = array(
'id' => $id,
);
return $this->db->delete($this->_name, $data);
}
function all() {
$query = $this->db->get($this->_name);
$results = array();
if($query->num_rows() > 0) {
foreach($query->result() as $row) {
$results[]=$row;
}
}
return $results;
}
}
Any help much appreciated...
What's the best way to check if the user has already submitted their data ?
Check if you already have a record for the user’s Facebook id in your database.