How to update database in moodle using cron - php

I am facing a problem. I have to update a field in database by using cron function in moodle. I am using update query in cron function to update value. But It doesn't work. I am using this function to update value:
function activitysetmodule_cron ()
{
global $CFG, $DB;
$DB->update_record("activitysetmodule",)
$sql="update {$CFG->prefix}activitysetmodule as asm set status = 1 where exists (select 1 from {$CFG->prefix}course_modules as cm where (module=asm.activityset OR module=asm.activityset2 ) AND completion=1 AND asm.course =cm.course ");
return true;
}
Please help to sought it out.

Take a look at the documentation https://docs.moodle.org/dev/Data_manipulation_API#Updating_Records
$DB->update_record takes 2 params, the name of the table to update the record in and an object containing the updated data.
e.g.
$obj = new stdClass();
$obj->id = $id_of_object_to_update;
$obj->status = 1;
$DB->update_record('tablename', $obj);
It looks like you should refactor your code to get a list of records to update, then call $DB->update_record on each in turn (or $DB->set_field, if there is only one field to update). Alternatively, you could use the $DB->execute($sql) function to directly run some SQL on the server, e.g.
$DB->execute("UPDATE {activitysetmodule} asm SET status = 1 WHERE EXISTS (SELECT 1 FROM {course_modules} cm WHERE (module=asm.activityset OR module=asm.activityset2 ) AND completion=1 AND asm.course = cm.course)");
Note the use of {tablename} rather than {$CFG->prefix}tablename and the removal of the 'AS' keyword, as that is not allowed on all DB engines.
Note also, if you haven't done so already, turning on debugging (http://docs.moodle.org/en/Debugging) will give you much more helpful error messages.

Related

Flowgear configurable variable bar for sql query via API

I have a datase table with a list of books. Below is my sql statement:
SELECT `Book`.`id` , `Book`.`name` , `Book`.`isbn` , `Book`.`quantity_in_stock` , `Book`.`price` , (`Book`.`quantity_in_stock` * `Book`.`price`) AS `sales`, concat(`Author`.`name`, ' ', `Author`.`surname`) AS `author`
FROM `books` AS `Book`
LEFT JOIN authors AS `Author`
ON ( `Book`.`author_id` = `Author`.`id` )
WHERE (`Book`.`quantity_in_stock` * `Book`.`price`) > 5000.00
The query works fine and the workflow works fine too. However, I am wanting to access this through an API and make the 5000.00 value configurable through a variable bar.
Question is how do I make this possible such that when I call my API with my endpoint below it works?
https://domain.flowgear.io/5000booklist/{sales_value}
What I want is to be able to re-use my workflow via an API and just pass a sales value I want to query the table against. Sales value can be 2000 or 5000 depending on what I want to achieve.
Add a variable bar and add a property to it called "salesValue"
In the workflow detail pane, provide this url: "/booklist/{salesValue}" - the value in braces must match the name of the property in the variable bar
Add a Formatter, put your SQL template including "WHERE (Book.quantity_in_stock * Book.price) > {salesValue}" in the Expression property then add a custom field called salesValue and pin that from the variable bar salesValue property. Set Escaping to SQL.
Take the output of the Formatter and plug that into the SQL Query property of a SQL Query Connector.
Add another variable bar, and add the special properties FgResponseBody and FgResponseContentType
Pin the SQL result to FgResponseBody and set FgResponseContentType to 'text/xml'
If you want to return JSON, convert the result from the SQL Query to JSON using JSON Convert and then pin that to FgResponseBody and set FgResponseContentType to 'application/json'
#sanjay I will try to give you an overview of what I did back then when I was experimenting with Flowgear through PHP following instructions from here.
I am not sure if you are also invoking the Flowgear REST API through PHP or any other language but regardless I presume logic should remain the same.
What I did was to wrap the PHP CURL sample code in a class so that I can be able to reuse it. Below is a code I wrote for a simple select query:
<?php
//Require the FlowgearConnect class
require_once '/path/to/flowgear_class_with_api_call.php';
try{
$workflow = new FlowgearConnect(return include 'endpoints.php');
$serial = $_POST['serial'];
$clientId = $_POST['client_id'];
//Get the results
$sql = '';
if(empty($serial)){
$conditions = sprintf(' `a`.`client_id` = %s AND `a`.`serial` > -1 ORDER BY `a`.`serial` ASC', $clientId);
}else{
$conditions = ' `a`.`serial` = ' . $serial;
}
/**
In your workflow you will most probably have a VARIABLE BAR that holds your request parameters which is what $conditions speaks to.
*/
$conditions = array('conditions' => $conditions);
$results = $workflow->getResults('orders', 'orders', $conditions);
}catch(catch any exceptions thrown by the API here){
//Log the exceptions here or do whatever
}
The listing above should be self explanatory. Below I will show you the functions I have made use of from my FlowgearConnect class. This is not a standard way as you may configure your code differently to suite your needs.
//FlowgearConnect constructor
class FlowgearConnect
{
protetced $endpoints = [];
protected $domain = "https://your-domain.flowgear.io";
public function __construct(array $endpoints)
{
$this->endpoints = $endpoints;
}
public function getResults($model, $workflow, $options= array())
{
$endpoint = $this->getEndpoint($model, $workflow);
$results = array();
if(!empty($endpoint)){
$results = FlowgearInvoke::run($authOpts, $endpoint, $options, array('timeout' => 30));
}
return $results;
}
....
}
The enpoints.php file, as mentioned before, just returns an array of configured endpoints and/or worflow names from within flowgear console. Below is a excerpt of how mine looked like:
return array(
'orders' => array(
'shipped_orders' => '/shipped_orders',
//etc
),
'items' => array(
'your_model' => '/workflow_name_from_flowgear_console',
),
);
This is just a basic select query with Flowgear's REST API using PHP. If you are lucky you should get your records the way you have configured your response body for your workflow.
Below is a typical testing of a workflow and what you should get back in your API.
I advice you to first create your workflows on your flowgear console and make sure that the produce the desired output and the extract the parts that you want changed no your query, move them to a variable bar for your request and have them injected at run-time based on what you looking to achieve. This explanation can be substituted for other operations such as update and/or delete. Best thing is to understand flowgear first and make sure that you can have everything working there before attempting to create a restful interactive application.
Caution: It's over a year that I have since worked with this platform so you might find errors in this but I am hoping that it will lead you to finding a solution for your problem. If not then perhaps you can create a repo and have me check it out to see how you are configuring everything.

Yii DataProvider change the attributes of a column in each result

I'm using Yii's Dataprovider to output a bunch of users based on the column "points";
It works fine now but I have to add a feature so if the user is online, he gets an extra 300 points.
Say Jack has 100 points, Richmond has 300 points, However Jack is online, so Jack should rank higher than Richmond.
Here is my solution now:
$user=new Rank('search');
$user->unsetAttributes();
$user->category_id = $cid;
$dataProvider = $user->search();
$iterator = new CDataProviderIterator($dataProvider);
foreach($iterator as $data) {
//check if online ,update points
}
However, this CDataProviderIterator seems change my pagination directly to the last page and I can't even switch page anymore. What should I do?
Thank you very much!
Here is the listview:
$this->widget('zii.widgets.CListView', array(
'id'=>'userslist',
'dataProvider'=>$dataProvider,
'itemView'=>'_find',
'ajaxUpdate'=>false,
'template'=>'{items}<div class="clear"></div><div style="margin-right:10px;"><br /><br />{pager}</div>',
'pagerCssClass'=>'right',
'sortableAttributes'=>array(
// 'create_time'=>'Time',
),
));
Updated codes in Rank.php model
$criteria->with = array('user');
$criteria->select = '*, (IF(user.lastaction > CURRENT_TIMESTAMP() - 1800, points+300, points)) as real_points';
$criteria->order = 'real_points DESC';
However, it throws me error:
Active record "Rank" is trying to select an invalid column "(IF(user.lastaction > CURRENT_TIMESTAMP() - 1800". Note, the column must exist in the table or be an expression with alias.
CDataProviderIterator iterates every dataprovider value, and stops at the end. I don't know all about this classes, but think the reason is in some internal iterator, that stops at the end of dataprovider after your foreach.
Iterators are used when you need not load all data (for large amounts of data) but need to process each row.
To solve your problem, just process data in your view "_find". Add points there if online.
Or if you want place this logic only in the model (following MVC :) ), add method to your model:
public function getRealPoints() {
return ($this->online) ? ($this->points + 300) : $this->points;
}
And you can use $user->realPoints to get points according to user online status
update: To order your list by "realPoints" you need to get it in your SQL.
So use your code:
$user=new Rank('search');
$user->unsetAttributes();
$user->category_id = $cid;
$dataProvider = $user->search();
and modify $user->search() function, by adding:
$criteria->select = '*, (IF(online='1', points+300, points)) as real_points';
$criteria->order = 'real_points DESC';
where online and points - your table columns.

update query for Mongodb in yii

How can I update based on _id in mongodb for YII?
What I tried is
$model= new MongoUrls();
$criteria = new EMongoCriteria;
$criteria->userid('==', $userid);
$criteria->screenshot_path('!=', null);
$criteria->screenshot_uploaded('!=', 1);
$availablescreenshots=$model-> findAll($criteria);
if(count($availablescreenshots)>0){
foreach($availablescreenshots as $obj1){
$path_parts = pathinfo($obj1->screenshot_path);
if($social->upload($obj1->screenshot_path, 'test',$path_parts['basename'])) {
$model->updateAll(array('_id'=>$obj1->_id ), array('screenshot_uploaded'=>1) );
}
}
}
But it shows an error "The EMongoDocument cannot be updated because it is new." in Yii .
I want to update a document where _id matches same value
If I am correct in assuming the extension you are using you actually want $model->updateAll() since update() relates to updating the current active record not to running a general query. It is a bit confusing but it is the way Yii works.
As yii mongosuite docs states, updateAll is a bit different in use than usual update. Also, you are using updateAll in loop and as condition you pass single id which not really makes sense. With updateAll you could use criteria to update models. Here you should use partial update like that:
// _id is already set because it comes from db
$obj1->screenshot_uploaded = 1;
// First param to set fields which should be updated
// Set second param to true, to make partial update
$obj1->update(array('screenshot_uploaded'), true);
The method worked for me was
$modifier = new EMongoModifier();
$modifier->addModifier('screenshot_uploaded', 'set', '1');
$criteria = new EMongoCriteria();
$criteria->addCond('_id','==', $obj1->_id );
$model->updateAll($modifier,$criteria );

PHP RedBean store bean if not exist one

I am a little confused. I actively use PHP RedBean as ORM within my direct mail service and I run into curious situation - I have a table with unique key constraint (i.e. subscriber_id, delivery_id) and two scripts that is writing data into this table.
There is source code that is inserting or updating table:
public static function addOpenPrecedent($nSubscriberId, $nDeliveryId)
{
$oOpenStatBean = \R::findOrDispense('open_stat', 'delivery_id = :did AND subscriber_id = :sid', array(':did' => $nDeliveryId, ':sid' => $nSubscriberId));
$oOpenStatBean = array_values($oOpenStatBean);
if (1 !== count($oOpenStatBean)) {
throw new ModelOpenStatException(
"Ошибка при обновлении статистики открытий: пара (delivery_id,
subscriber_id) не является уникальной: ($nDeliveryId, $nSubscriberId).");
}
$oOpenStatBean = $oOpenStatBean[0];
if (!empty($oOpenStatBean->last_add_dt)) {
$oOpenStatBean->precedent++;
} else {
$oOpenStatBean->delivery_id = $nDeliveryId;
$oOpenStatBean->subscriber_id = $nSubscriberId;
}
$oOpenStatBean->last_add_dt = time('Y-m-d H:i:s');
\R::store($oOpenStatBean);
}
It is called both from two scripts. And I have issues with corruption unique constraint on this table periodically, because race conditions occurs. I know about SQL "INSERT on duplicate key update" feature. But how can I obtain same result purely using my ORM?
Current, that I know if, Redbean will not issue an
INSERT ON DUPLICATE KEY UPDATE
as the discussion of this cited in the comments above indicates that Redbean's developer considers upsert to be a business logic thing that would pollute the ORM's interphase. This being said, it is most likely achievable if one were to extend Redbean with a custom Query Writer or plugin per the Documentation. I haven't tried this because the method below easily achieves this behavior without messing with the internals and plugins of the ORM, however, it does require that you use transactions and models and a couple of extra queries.
Basically, start your transaction with either R::transaction() or R::begin() before your call to R::store(). Then in your "FUSE"d model, use the "update" FUSE method to run a query that checks for duplication and retrieves the existing id while locking the necessary rows (i.e. SELECT FOR UPDATE). If no id is returned, you are good and just let your regular model validation (or lack thereof) continue as usual and return. If an id is found, simply set $this->bean->id to the returned value and Redbean will UPDATE rather than INSERT. So, with a model like this:
class Model_OpenStat extends RedBean_SimpleModel{
function update(){
$sql = 'SELECT * FROM `open_stat` WHERE `delivery_id`=? AND 'subscriber_id'=? LIMIT 1 FOR UPDATE';
$args = array( $this->bean->deliver_id, $this->bean->subscriber_id );
$dupRow = R::getRow( $sql, $args );
if( is_array( $dupRow ) && isset( $dupRow['id'] ) ){
foreach( $this->bean->getProperties() as $property => $value ){
#set your criteria here for which fields
#should be from the one in the database and which should come from this copy
#this version simply takes all unset values in the current and sets them
#from the one in the database
if( !isset( $value ) && isset( $dupRow[$property] ) )
$this->bean->$property = $dupRow[$property];
}
$this->bean->id = $dupId['id']; #set id to the duplicates id
}
return true;
}
}
You would then modify the R::store() call like so:
\R::begin();
\R::store($oOpenStatBean);
\R::commit();
or
\R::transaction( function() use ( $oOpenStatBean ){ R::store( $oOpenStatBean ); } );
The transaction will cause the "FOR UPDATE" clause to lock the found row or, in the event that no row was found, to lock the places in the index where your new row will go so that you don't have concurrency issues.
Now this will not solve one user's update of the record clobbering another, but that is a whole different topic.

CakePHP & MVC – Potentially superfluous SQL queries when looking up 'names' associated with ids

I've probably murdered the whole concept of MVC somewhere along the line, but my current situation is thus:
I have participants in events and a HABTM relationship between them (with an associated field money_raised). I have a controller that successfully creates new HABTM relationships between pre-existing events and participants which works exactly as I want it to.
When a new relationship is created I wish to set the flash to include the name of the participant that has just been added. The actually addition is done using ids, so I've used the following code:
public function addParticipantToEvent($id = null) {
$this->set('eventId', $id);
if ($this->request->is('post')) {
if ($this->EventsParticipant->save($this->request->data)) {
$participant_id = $this->request->data['EventsParticipant']['participant_id'];
$money_raised = $this->request->data['EventsParticipant']['money_raised'];
$participant_array = $this->EventsParticipant->Participant->findById($participant_id);
$participant_name = $participant_array['Participant']['name'];
$this->Session->setFlash('New participant successfully added: ' . $participant_name . ' (' . $participant_id . ') ' . '— £' . $money_raised);
} else {
$this->Session->setFlash('Unable to create your event-participant link.');
}
}
}
This works, but generates the following SQL queries:
INSERT INTO `cakephptest`.`cakephptest_events_participants` (`event_id`, `participant_id`, `money_raised`) VALUES (78, 'crsid01', 1024) 1 1 0
SELECT `Participant`.`id`, `Participant`.`name`, `Participant`.`college` FROM `cakephptest`.`cakephptest_participants` AS `Participant` WHERE `Participant`.`id` = 'crsid01' LIMIT 1 1 1 0
SELECT `Event`.`id`, `Event`.`title`, `Event`.`date`, `EventsParticipant`.`id`, `EventsParticipant`.`event_id`, `EventsParticipant`.`participant_id`, `EventsParticipant`.`money_raised` FROM `cakephptest`.`cakephptest_events` AS `Event` JOIN `cakephptest`.`cakephptest_events_participants` AS `EventsParticipant` ON (`EventsParticipant`.`participant_id` = 'crsid01' AND `EventsParticipant`.`event_id` = `Event`.`id`)
This final one seems superfluous (and rather costly) as the second should give me all that I need, but removing $this->EventsParticipant->Participant->findById($participant_id) takes out both the second and third queries (which sort of makes sense to me, but not fully).
What can I do to remedy this redundancy (if indeed I'm not wrong that it is a redundancy)? Please tell me if I've made a complete hash of how these sorts of things should work – I'm very new to this.
This is probably due to the default recursive setting pulling the relationship. You can remedy this by setting public $recursive = -1; on your model (beware this will affect all find calls). Or, disable it temporarily for this find:
$this->EventsParticipant->Participant->recursive = -1;
$this->EventsParticipant->Participant->findById($participant_id);
I always suggest setting public $recursive = -1; on your AppModel and using Containable to bring in the related data where you need it.

Categories