I was hoping to do this with rules but falling short. Am I just spinning my wheels, or is it theoretically possible to trigger a rule on course object completion that redirects the user to the next object automatically?

Comments

djdevin’s picture

I don't think there's a way to do it with just the UI, however you can react on "Course object fulfillment" events, detect the completion (old:complete = 0, new:complete = 1) and use custom PHP to find the next object. It's possible we could make a Rules action out of it.

$course = course_get_course(course_get_context());
$current = $course->getActive();
$next = $course->getNext();
$prev = $course->getPrev();

$next_url = $next->getUrl(); // Will be "node/X/course-object/Y"
spelcheck’s picture

This is what I have so far, in case anybody is interested or has an idea-

RULE
Events
-After updating existing course object fulfillment (when a course object is viewed, or a course object is completed)
-After saving a new course object fulfillment (when a course object is clicked for the first time)

CONDITIONS
-User is enrolled (to make sure it only accesses the current users actions)
--Node Data selector: course-object-fulfillment:coid:nid
--User Data selector: site:current-user

ACTIONS
-Fetch entity by id (getting a course object id, either one that was created, or one that is being viewed. this is the path to the node ID of the course, the course enrollment, the course outline)
--Value: Course Object
--Data Selector: course-object-fulfillment:coid:coid
--I named it 'coid' instead of the generic 'fetched-entity'

-Show a message on the site
--Value:

dsm($course_object_fulfillment);
dsm($coid);
$results = db_query('SELECT usc_vp.course_enrollment.nid, usc_vp.course_outline.coid FROM {usc_vp.course_outline} RIGHT OUTER JOIN {usc_vp.course_enrollment} ON usc_vp.course_outline.nid = usc_vp.course_enrollment.nid LEFT OUTER JOIN {usc_vp.course_outline_fulfillment }  ON usc_vp.course_outline.coid = usc_vp.course_outline_fulfillment.coid AND usc_vp.course_outline_fulfillment.uid = usc_vp.course_enrollment.uid WHERE usc_vp.course_outline.nid = :nid and usc_vp.course_enrollment.uid = :uid and usc_vp.course_outline_fulfillment.complete <> 1 ORDER BY usc_vp.course_outline.weight ASC LIMIT 1', array(':nid' => $coid->nid, ':uid' => $course_object_fulfillment->uid))->fetchAll();
dsm($results);

// the query takes the current-user UID and the NID from the course object (which is shared by the course objects, the course, the course outline, and course enrollment)
and returns the first course object that hasn't yet been started. The plan is it would recognize when a course object is completed, traverse through and find the next non-competed course object and navigate the user to it. This is done based on the course-object weight. Navigating to a course-object that hasn't been started yet is actually what initially creates its entry in the course object fulfillment table (what keeps track of progress on the course-object), so because of that, I understand this rule will be triggered twice. Maybe down the road this can be broken up into two rules, one for each of the rule events I used at the top.

-----

The idea is that we would be using this Rule to get the NID and the UID for a redirect, so that whenever the user navigates to a course object, or completes a course object the system automatically redirects them to the next available object. The SQL query would need to be a bit smarter, to check whether or not the entire course is completed (probably looking at course_enrollment section column for the word 'Complete') and if so maybe not run, but it's a start. Redirect would be something like 'node/[nid]/course-object/[course-object-id]

If anyone knows of a better way to do this, I'd love to use Rules without hacking things to make it work.

elizzle

spelcheck’s picture

djdevin, that looks soo much simpler. I completely overlooked all of the built in course module routines. I'll give this a shot (and I'll leave my mess in case somebody wants a good laugh.) thank you!

djdevin’s picture

Ha, yeah :) Let me know what you come up with. There isn't much traditional documentation on the Course API however there's a lot of DX once you get into the code with an IDE.

It would be nice to have a Rules action which takes in a course object, and attempts to goes to the prev/next one.

I think you could also simplify it with

// $coid is from Rules, it is the ID of the course object, which is on the Fulfillment as well
$co = course_get_course_object_by_id($coid);
$url = $co->getCourse()->getNext()->getUrl();
wwedding’s picture

Assigned: Unassigned » wwedding

I think I'll have a patch to contribute for this; I've got something that works so far (https://www.drupal.org/sandbox/stickywes/2534194).

djdevin’s picture

Hmm some of that code seems copied from Rules.

Wouldn't it be better to build a Rules action (or token) that gets the URL of the next object?

That way you can use the built in Rules redirect action, or any other action - send email for example.

wwedding’s picture

Indeed, that seems a better approach.

wwedding’s picture

Oh, whoops! I never submitted the patch. I'll correct that soon-ish.

djdevin’s picture

wwedding’s picture

This patch provides a rule action that can be used to automatically step over to the next object in a course outline.

The action simply loads the course object of a passed-in course object fulfillment and returns the getURL() value of the next object or NULL if there is no next object.

An example usage can be seen using the Rules import below. When a course object fulfillment is updated, it is checked for completion, and then uses the built-in system redirect rule action to redirect after invoking the new Course rule action.

Use more conditionals to make it match your preferences.

{ "rules_go_to_next_course_object" : {
    "LABEL" : "Go to next course object",
    "PLUGIN" : "reaction rule",
    "OWNER" : "rules",
    "REQUIRES" : [ "rules", "course", "entity" ],
    "ON" : { "course_object_fulfillment_update" : [] },
    "IF" : [
      { "data_is" : { "data" : [ "course-object-fulfillment:complete" ], "value" : "1" } }
    ],
    "DO" : [
      { "course_next_url" : {
          "USING" : { "cof_entity" : [ "course-object-fulfillment" ] },
          "PROVIDE" : { "course_url_fetched" : { "course_url_fetched" : "Fetched Course object URL." } }
        }
      },
      { "redirect" : { "url" : [ "course-url-fetched" ] } }
    ]
  }
}
wwedding’s picture

Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, 10: course-add_next_url_rule-2418983-10-7.patch, failed testing.

Status: Needs work » Needs review
djdevin’s picture

Thanks, let me review.

It would be ideal if this could be used to get the next/previous *anything*, whether it be URL, the object title, if it was required or not, etc. i.e. not hard coding this to just the next URL.

wwedding’s picture

Just update it to return the whole object, then?

Edit: Nevermind, that wouldn't help what we're trying to make happen in this specific issue.

With just returning the whole object you could get a uri using 'course-obj-fetched:nid:url' in the rule definition but that wouldn't quite be right, because you would get 'node/###' instead of 'course/XXXXX/course-object/YYYYY'.

Are there any reasons I shouldn't define a uri callback for CourseObject entities?

wwedding’s picture

I took another look at this and realized that adding a next/previous property to the course entity works just fine, without needing to add a new rule. I'll have a patch up soonish.

wwedding’s picture

Status: Needs review » Needs work
wwedding’s picture

Related issues: +#2668800: Course Objects lack a URI property
StatusFileSize
new1.85 KB

Here's a patch that used to work for this issue's specific request until the course object entity URIs went away several commits ago #2668800: Course Objects lack a URI property.

This adds a next/previous property that enables you to get a course object.

You might be able to use the id of the course object to load a node and then redirect to its URL property, but that's kind of a weird extra step to have to take and it requires knowing that a node is the underlying entity instead of some other entity you might use in the future.