Index: trunk/extensions/EducationProgram/EducationProgram.i18n.alias.php |
— | — | @@ -34,6 +34,7 @@ |
35 | 35 | 'OnlineAmbassadors' => array( 'OnlineAmbassadors' ), |
36 | 36 | 'CampusAmbassador' => array( 'CampusAmbassador' ), |
37 | 37 | 'OnlineAmbassador' => array( 'OnlineAmbassador' ), |
| 38 | + 'CourseHistory' => array( 'CourseHistory' ), |
38 | 39 | ); |
39 | 40 | |
40 | 41 | /** Dutch (Nederlands) */ |
Index: trunk/extensions/EducationProgram/sql/EducationProgram.sql |
— | — | @@ -163,9 +163,10 @@ |
164 | 164 | |
165 | 165 | -- Revision table, holding blobs of various types of objects, such as orgs or students. |
166 | 166 | -- This is somewhat based on the (core) revision table and is meant to serve |
| 167 | +-- as a prototype for a more general system to store this kind of data in a visioned fashion. |
167 | 168 | CREATE TABLE IF NOT EXISTS /*_*/ep_revisions ( |
168 | 169 | rev_id INT unsigned NOT NULL auto_increment PRIMARY KEY, |
| 170 | + rev_object_id INT unsigned NOT NULL, |
169 | 171 | rev_type varbinary(32) NOT NULL, |
170 | 172 | rev_comment TINYBLOB NOT NULL, |
171 | 173 | rev_user_id INT unsigned NOT NULL default 0, |
— | — | @@ -176,6 +177,7 @@ |
177 | 178 | rev_data BLOB NOT NULL |
178 | 179 | ) /*$wgDBTableOptions*/; |
179 | 180 | |
| 181 | +CREATE INDEX /*i*/ep_revision_object_id ON /*_*/ep_revisions (rev_object_id); |
180 | 182 | CREATE INDEX /*i*/ep_revision_type ON /*_*/ep_revisions (rev_type); |
181 | 183 | CREATE INDEX /*i*/ep_revision_user_id ON /*_*/ep_revisions (rev_user_id); |
182 | 184 | CREATE INDEX /*i*/ep_revision_user_text ON /*_*/ep_revisions (rev_user_text); |
Index: trunk/extensions/EducationProgram/sql/AddRevisionObjectId.sql |
— | — | @@ -0,0 +1,7 @@ |
| 2 | +-- SQL for the Education Program extension. |
| 3 | +-- Add object id field to revision table. |
| 4 | +-- Licence: GNU GPL v3+ |
| 5 | +-- Author: Jeroen De Dauw < jeroendedauw@gmail.com > |
| 6 | + |
| 7 | +ALTER TABLE /*_*/ep_revisions ADD COLUMN rev_object_id INT unsigned NOT NULL; |
| 8 | +CREATE INDEX /*i*/ep_revisions_object_id ON /*_*/ep_revisions (rev_object_id); |
Index: trunk/extensions/EducationProgram/specials/SpecialCourseHistory.php |
— | — | @@ -0,0 +1,47 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Special page for listing the history of a course. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file SpecialCourseHistory.php |
| 10 | + * @ingroup EducationProgram |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class SpecialCourseHistory extends SpecialEPHistory { |
| 16 | + |
| 17 | + /** |
| 18 | + * Constructor. |
| 19 | + * |
| 20 | + * @since 0.1 |
| 21 | + */ |
| 22 | + public function __construct() { |
| 23 | + parent::__construct( 'CourseHistory', '', false ); |
| 24 | + } |
| 25 | + |
| 26 | + /** |
| 27 | + * Main method. |
| 28 | + * |
| 29 | + * @since 0.1 |
| 30 | + * |
| 31 | + * @param string $subPage |
| 32 | + */ |
| 33 | + public function execute( $subPage ) { |
| 34 | + parent::execute( $subPage ); |
| 35 | + |
| 36 | + $course = EPCourse::selectRow( null, array( 'id' => $subPage ) ); |
| 37 | + |
| 38 | + if ( $course === false ) { |
| 39 | + // TODO |
| 40 | + } |
| 41 | + else { |
| 42 | + $this->displayRevisions( $course ); |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + |
| 47 | + |
| 48 | +} |
Index: trunk/extensions/EducationProgram/specials/SpecialEPFormPage.php |
— | — | @@ -1,7 +1,7 @@ |
2 | 2 | <?php |
3 | 3 | |
4 | 4 | /** |
5 | | - * Extends FormSpecialPage with commons functions needed in EducationProgram. |
| 5 | + * FormSpecialPage equivalent but deriving from SpecialEPPage. |
6 | 6 | * |
7 | 7 | * @since 0.1 |
8 | 8 | * |
Index: trunk/extensions/EducationProgram/specials/SpecialEPHistory.php |
— | — | @@ -0,0 +1,51 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Base class for history special pages. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file SpecialEPHistory.php |
| 10 | + * @ingroup EducationProgram |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +abstract class SpecialEPHistory extends SpecialEPPage { |
| 16 | + |
| 17 | + protected function displayRevisions( EPDBObject $object ) { |
| 18 | + $conditions = array( |
| 19 | + 'type' => get_class( $object ), |
| 20 | + ); |
| 21 | + |
| 22 | + if ( $object->hasIdField() ) { |
| 23 | + $conditions['object_id'] = $object->getId(); |
| 24 | + } |
| 25 | + |
| 26 | + $revisions = EPRevision::select( |
| 27 | + null, |
| 28 | + $conditions |
| 29 | + ); |
| 30 | + |
| 31 | + if ( count( $revisions ) > 0 ) { |
| 32 | + array_unshift( $revisions, EPRevision::newFromObject( $object ) ); |
| 33 | + $this->displayRevisionList( $revisions ); |
| 34 | + } |
| 35 | + else { |
| 36 | + // TODO |
| 37 | + } |
| 38 | + } |
| 39 | + |
| 40 | + protected function displayRevisionList( array /* of EPRevision */ $revisions ) { |
| 41 | + foreach ( $revisions as &$revision ) { |
| 42 | + $revision = '<li>' . $this->getRevisionItem( $revision ) . '</li>'; |
| 43 | + } |
| 44 | + |
| 45 | + $this->getOutput()->addHTML( '<ul>' . implode( '', $revisions ) . '</ul>' ); |
| 46 | + } |
| 47 | + |
| 48 | + protected function getRevisionItem( EPRevision $revision ) { |
| 49 | + return $revision->getField( 'time' ) . json_encode( $revision->getField( 'data' ) ); // TODO |
| 50 | + } |
| 51 | + |
| 52 | +} |
Index: trunk/extensions/EducationProgram/includes/EPDBObject.php |
— | — | @@ -60,6 +60,14 @@ |
61 | 61 | protected $log = true; |
62 | 62 | |
63 | 63 | /** |
| 64 | + * If the object should store old revisions. |
| 65 | + * |
| 66 | + * @since 0.1 |
| 67 | + * @var bool |
| 68 | + */ |
| 69 | + protected $storeRevisions = true; |
| 70 | + |
| 71 | + /** |
64 | 72 | * The database connection to use for read operations. |
65 | 73 | * Can be changed via @see setReadDb. |
66 | 74 | * |
— | — | @@ -320,7 +328,9 @@ |
321 | 329 | |
322 | 330 | switch ( $type ) { |
323 | 331 | case 'array': |
324 | | - $value = serialize( (array)$value ); |
| 332 | + $value = (array)$value; |
| 333 | + case 'blob': |
| 334 | + $value = serialize( $value ); |
325 | 335 | } |
326 | 336 | |
327 | 337 | $values[$this->getFieldPrefix() . $name] = $value; |
— | — | @@ -393,13 +403,14 @@ |
394 | 404 | |
395 | 405 | /** |
396 | 406 | * Updates the object in the database. |
397 | | - * TODO: store old rev |
398 | 407 | * |
399 | 408 | * @since 0.1 |
400 | 409 | * |
401 | 410 | * @return boolean Success indicator |
402 | 411 | */ |
403 | 412 | protected function updateInDB() { |
| 413 | + $this->storeRevision(); |
| 414 | + |
404 | 415 | $dbw = wfGetDB( DB_MASTER ); |
405 | 416 | |
406 | 417 | $success = $dbw->update( |
— | — | @@ -416,6 +427,16 @@ |
417 | 428 | return $success; |
418 | 429 | } |
419 | 430 | |
| 431 | + protected function storeRevision( $isDelete = false ) { |
| 432 | + static::setReadDb( DB_MASTER ); |
| 433 | + $revison = static::selectRow( null, array( 'id' => $this->getId() ) ); |
| 434 | + static::setReadDb( DB_SLAVE ); |
| 435 | + |
| 436 | + $revison = EPRevision::newFromObject( $revison, $isDelete ); |
| 437 | + |
| 438 | + $revison->writeToDB(); |
| 439 | + } |
| 440 | + |
420 | 441 | /** |
421 | 442 | * Inserts the object into the database. |
422 | 443 | * |
— | — | @@ -443,13 +464,14 @@ |
444 | 465 | |
445 | 466 | /** |
446 | 467 | * Removes the object from the database. |
447 | | - * TODO: store rev |
448 | 468 | * |
449 | 469 | * @since 0.1 |
450 | 470 | * |
451 | 471 | * @return boolean Success indicator |
452 | 472 | */ |
453 | 473 | public function removeFromDB() { |
| 474 | + $this->storeRevision( true ); |
| 475 | + |
454 | 476 | $success = $this->delete( array( 'id' => $this->getId() ) ); |
455 | 477 | |
456 | 478 | if ( $success ) { |
— | — | @@ -552,6 +574,11 @@ |
553 | 575 | $value = array(); |
554 | 576 | } |
555 | 577 | break; |
| 578 | + case 'blob': |
| 579 | + if ( is_string( $value ) ) { |
| 580 | + $value = unserialize( $value ); |
| 581 | + } |
| 582 | + break; |
556 | 583 | case 'id': |
557 | 584 | if ( is_string( $value ) ) { |
558 | 585 | $value = (int)$value; |
— | — | @@ -1132,7 +1159,8 @@ |
1133 | 1160 | 'float' => 'NULL', |
1134 | 1161 | 'str' => 'string', |
1135 | 1162 | 'bool' => 'integer', |
1136 | | - 'array' => 'string' |
| 1163 | + 'array' => 'string', |
| 1164 | + 'blob' => 'string', |
1137 | 1165 | ); |
1138 | 1166 | |
1139 | 1167 | $params = array(); |
— | — | @@ -1207,6 +1235,17 @@ |
1208 | 1236 | } |
1209 | 1237 | |
1210 | 1238 | /** |
| 1239 | + * Sets the value for the @see $storeRevisions field. |
| 1240 | + * |
| 1241 | + * @since 0.1 |
| 1242 | + * |
| 1243 | + * @param boolean $store |
| 1244 | + */ |
| 1245 | + public function setStoreRevisions( $store ) { |
| 1246 | + $this->storeRevisions = $store; |
| 1247 | + } |
| 1248 | + |
| 1249 | + /** |
1211 | 1250 | * Sets the value for the @see $log field. |
1212 | 1251 | * |
1213 | 1252 | * @since 0.1 |
Index: trunk/extensions/EducationProgram/includes/EPRevisions.php |
— | — | @@ -0,0 +1,24 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Static class for storing and retrieving revisions of EPDBObjects. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file EPRevisions.php |
| 10 | + * @ingroup EducationProgram |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +final class EPRevisions { |
| 16 | + |
| 17 | + public static function storeRevision() { |
| 18 | + |
| 19 | + } |
| 20 | + |
| 21 | + public static function getRevision() { |
| 22 | + |
| 23 | + } |
| 24 | + |
| 25 | +} |
Index: trunk/extensions/EducationProgram/includes/EPRevision.php |
— | — | @@ -0,0 +1,80 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Class representing a single revision. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file EPRevision.php |
| 10 | + * @ingroup EducationProgram |
| 11 | + * |
| 12 | + * @licence GNU GPL v3 or later |
| 13 | + * @author Jeroen De Dauw < jeroendedauw@gmail.com > |
| 14 | + */ |
| 15 | +class EPRevision extends EPDBObject { |
| 16 | + |
| 17 | + /** |
| 18 | + * @see parent::__construct |
| 19 | + * |
| 20 | + * @since 0.1 |
| 21 | + * |
| 22 | + * @param array|null $fields |
| 23 | + * @param bool $loadDefaults |
| 24 | + */ |
| 25 | + public function __construct( $fields = null, $loadDefaults = false ) { |
| 26 | + $this->setStoreRevisions( false ); |
| 27 | + parent::__construct( $fields, $loadDefaults ); |
| 28 | + } |
| 29 | + |
| 30 | + /** |
| 31 | + * @see parent::getFieldTypes |
| 32 | + * |
| 33 | + * @since 0.1 |
| 34 | + * |
| 35 | + * @return array |
| 36 | + */ |
| 37 | + protected static function getFieldTypes() { |
| 38 | + return array( |
| 39 | + 'id' => 'id', |
| 40 | + |
| 41 | + 'object_id' => 'id', |
| 42 | + 'user_id' => 'id', |
| 43 | + 'type' => 'str', |
| 44 | + 'comment' => 'str', |
| 45 | + 'user_text' => 'str', |
| 46 | + 'minor_edit' => 'bool', |
| 47 | + 'time' => 'str', // TS_MW |
| 48 | + 'deleted' => 'bool', |
| 49 | + 'data' => 'blob', |
| 50 | + ); |
| 51 | + } |
| 52 | + |
| 53 | + /** |
| 54 | + * Create a new revision object for the provided EPDBObject. |
| 55 | + * The EPDBObject should have all it's fields loaded. |
| 56 | + * |
| 57 | + * @since 0.1 |
| 58 | + * |
| 59 | + * @param EPDBObject $object |
| 60 | + * @param boolean $deleted |
| 61 | + * |
| 62 | + * @return EPRevision |
| 63 | + */ |
| 64 | + public static function newFromObject( EPDBObject $object, $deleted = false ) { |
| 65 | + $fields = array( |
| 66 | + 'object_id' => $object->getId(), |
| 67 | + 'user_id' => $GLOBALS['wgUser']->getID(), // TODO |
| 68 | + 'user_text' => $GLOBALS['wgUser']->getName(), // TODO |
| 69 | + 'type' => get_class( $object ), |
| 70 | + 'comment' => '', // TODO |
| 71 | + 'minor_edit' => false, // TODO |
| 72 | + 'time' => wfTimestampNow(), |
| 73 | + 'deleted' => $deleted, |
| 74 | + 'data' => serialize( $object->toArray() ) |
| 75 | + ); |
| 76 | + |
| 77 | + return new static( $fields ); |
| 78 | + } |
| 79 | + |
| 80 | + |
| 81 | +} |
Index: trunk/extensions/EducationProgram/EducationProgram.i18n.php |
— | — | @@ -25,9 +25,9 @@ |
26 | 26 | 'ep-toplink' => 'My courses', |
27 | 27 | |
28 | 28 | // Tabs |
29 | | - 'ep-tab-view' => 'View', |
| 29 | + 'ep-tab-view' => 'Read', |
30 | 30 | 'ep-tab-edit' => 'Edit', |
31 | | - 'ep-tab-history' => 'history', |
| 31 | + 'ep-tab-history' => 'View history', |
32 | 32 | 'ep-tab-enroll' => 'Enroll', |
33 | 33 | |
34 | 34 | // Tooltips |
— | — | @@ -405,6 +405,7 @@ |
406 | 406 | 'ep-mycourses-not-a-student' => 'You are not enrolled in any [[Special:Courses|courses]].', |
407 | 407 | |
408 | 408 | // ep.instructor |
| 409 | + // <script>alert("XSS");</script> |
409 | 410 | 'ep-instructor-remove-title' => 'Remove instructor from master course', |
410 | 411 | 'ep-instructor-remove-button' => 'Remove instructor', |
411 | 412 | 'ep-instructor-removing' => 'Removing...', |
Index: trunk/extensions/EducationProgram/EducationProgram.php |
— | — | @@ -81,6 +81,7 @@ |
82 | 82 | $wgAutoloadClasses['EPCA'] = dirname( __FILE__ ) . '/includes/EPCA.php'; |
83 | 83 | $wgAutoloadClasses['EPCAPager'] = dirname( __FILE__ ) . '/includes/EPCAPager.php'; |
84 | 84 | $wgAutoloadClasses['EPHTMLDateField'] = dirname( __FILE__ ) . '/includes/EPHTMLDateField.php'; |
| 85 | +$wgAutoloadClasses['EPRevision'] = dirname( __FILE__ ) . '/includes/EPRevision.php'; |
85 | 86 | |
86 | 87 | $wgAutoloadClasses['SpecialCourse'] = dirname( __FILE__ ) . '/specials/SpecialCourse.php'; |
87 | 88 | $wgAutoloadClasses['SpecialCourses'] = dirname( __FILE__ ) . '/specials/SpecialCourses.php'; |
— | — | @@ -102,6 +103,8 @@ |
103 | 104 | $wgAutoloadClasses['SpecialOAs'] = dirname( __FILE__ ) . '/specials/SpecialOAs.php'; |
104 | 105 | $wgAutoloadClasses['SpecialCA'] = dirname( __FILE__ ) . '/specials/SpecialCA.php'; |
105 | 106 | $wgAutoloadClasses['SpecialOA'] = dirname( __FILE__ ) . '/specials/SpecialOA.php'; |
| 107 | +$wgAutoloadClasses['SpecialEPHistory'] = dirname( __FILE__ ) . '/specials/SpecialEPHistory.php'; |
| 108 | +$wgAutoloadClasses['SpecialCourseHistory'] = dirname( __FILE__ ) . '/specials/SpecialCourseHistory.php'; |
106 | 109 | |
107 | 110 | // Special pages |
108 | 111 | $wgSpecialPages['MyCourses'] = 'SpecialMyCourses'; |
— | — | @@ -122,6 +125,7 @@ |
123 | 126 | $wgSpecialPages['OnlineAmbassadors'] = 'SpecialOAs'; |
124 | 127 | $wgSpecialPages['CampusAmbassador'] = 'SpecialCA'; |
125 | 128 | $wgSpecialPages['OnlineAmbassador'] = 'SpecialOA'; |
| 129 | +$wgSpecialPages['CourseHistory'] = 'SpecialCourseHistory'; |
126 | 130 | |
127 | 131 | $wgSpecialPageGroups['MyCourses'] = 'education'; |
128 | 132 | $wgSpecialPageGroups['Institution'] = 'education'; |
— | — | @@ -140,9 +144,11 @@ |
141 | 145 | $wgSpecialPageGroups['OnlineAmbassadors'] = 'education'; |
142 | 146 | $wgSpecialPageGroups['CampusAmbassador'] = 'education'; |
143 | 147 | $wgSpecialPageGroups['OnlineAmbassador'] = 'education'; |
| 148 | +$wgSpecialPageGroups['CourseHistory'] = 'education'; |
144 | 149 | |
145 | 150 | // DB object classes |
146 | 151 | $egEPDBObjects = array(); |
| 152 | +$egEPDBObjects['EPRevision'] = array( 'table' => 'ep_revisions', 'prefix' => 'rev_' ); |
147 | 153 | $egEPDBObjects['EPOrg'] = array( 'table' => 'ep_orgs', 'prefix' => 'org_' ); |
148 | 154 | $egEPDBObjects['EPMC'] = array( 'table' => 'ep_mcs', 'prefix' => 'mc_' ); |
149 | 155 | $egEPDBObjects['EPCourse'] = array( 'table' => 'ep_courses', 'prefix' => 'course_' ); |
Index: trunk/extensions/EducationProgram/resources/ep.instructor.js |
— | — | @@ -83,7 +83,9 @@ |
84 | 84 | '<b>' + mw.html.escape( bestName ) + '</b>', |
85 | 85 | '<b>' + mw.html.escape( mcName ) + '</b>' |
86 | 86 | ) ); |
87 | | - |
| 87 | + |
| 88 | + //$dialog.append( $( '<p>' ).msg( 'ep-instructor-remove-title' ) ); |
| 89 | + |
88 | 90 | $dialog.append( summaryInput ); |
89 | 91 | |
90 | 92 | summaryInput.focus(); |
Index: trunk/extensions/EducationProgram/EducationProgram.hooks.php |
— | — | @@ -37,6 +37,14 @@ |
38 | 38 | true |
39 | 39 | ) ); |
40 | 40 | |
| 41 | + $updater->addExtensionUpdate( array( |
| 42 | + 'addField', |
| 43 | + 'ep_revisions', |
| 44 | + 'rev_object_id', |
| 45 | + dirname( __FILE__ ) . '/sql/AddRevisionObjectId.sql', |
| 46 | + true |
| 47 | + ) ); |
| 48 | + |
41 | 49 | return true; |
42 | 50 | } |
43 | 51 | |
— | — | @@ -179,7 +187,7 @@ |
180 | 188 | array( |
181 | 189 | 'view' => 'Course', |
182 | 190 | 'edit' => 'EditCourse', |
183 | | - //'history' => 'CourseHistory', |
| 191 | + 'history' => 'CourseHistory', |
184 | 192 | 'enroll' => 'Enroll', |
185 | 193 | ), |
186 | 194 | ); |
— | — | @@ -237,8 +245,7 @@ |
238 | 246 | $viewLinks['history'] = array( |
239 | 247 | 'class' => $type === 'history' ? 'selected' : false, |
240 | 248 | 'text' => wfMsg( 'ep-tab-history' ), |
241 | | - 'href' => '' // TODO |
242 | | - //SpecialPage::getTitleFor( $specialSet['history'], $textParts[1] )->getLocalUrl() |
| 249 | + 'href' => SpecialPage::getTitleFor( $specialSet['history'], $textParts[1] )->getLocalUrl() |
243 | 250 | ); |
244 | 251 | |
245 | 252 | if ( $canonicalSet['view'] === 'Course' ) { |