diff --git a/src/courseware/course/course-license/CourseLicense.jsx b/src/courseware/course/course-license/CourseLicense.jsx
new file mode 100644
index 00000000..9aa0d72d
--- /dev/null
+++ b/src/courseware/course/course-license/CourseLicense.jsx
@@ -0,0 +1,166 @@
+import React from 'react';
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import PropTypes from 'prop-types';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faCopyright } from '@fortawesome/free-regular-svg-icons';
+import {
+ faCreativeCommons,
+ faCreativeCommonsBy,
+ faCreativeCommonsNc,
+ faCreativeCommonsNd,
+ faCreativeCommonsSa,
+ faCreativeCommonsZero,
+} from '@fortawesome/free-brands-svg-icons';
+
+import messages from './messages';
+
+const CreativeCommonsLicenseTags = {
+ by: {
+ intlMessagesId: 'learn.course.license.creativeCommons.terms.by',
+ icon: faCreativeCommonsBy,
+ },
+ nc: {
+ intlMessagesId: 'learn.course.license.creativeCommons.terms.nc',
+ icon: faCreativeCommonsNc,
+ },
+ nd: {
+ intlMessagesId: 'learn.course.license.creativeCommons.terms.nd',
+ icon: faCreativeCommonsNd,
+ },
+ sa: {
+ intlMessagesId: 'learn.course.license.creativeCommons.terms.sa',
+ icon: faCreativeCommonsSa,
+ },
+ zero: {
+ intlMessagesId: 'learn.course.license.creativeCommons.terms.zero',
+ icon: faCreativeCommonsZero,
+ },
+};
+
+function parseLicense(license) {
+ if (!license) {
+ // Default to All Rights Reserved if no license
+ // is detected
+ return ['all-rights-reserved', {}];
+ }
+
+ // Search for a colon character denoting the end
+ // of the license type and start of the options
+ const colonIndex = license.indexOf(':');
+ if (colonIndex === -1) {
+ // no options, so the entire thing is the license type
+ return [license, {}];
+ }
+
+ // Split the license on the colon
+ const licenseType = license.slice(0, colonIndex).trim();
+ const optionStr = license.slice(colonIndex + 1).trim();
+
+ let options = {};
+ let version = '';
+
+ // Set the defaultVersion to 4.0
+ const defaultVersion = '4.0';
+ optionStr.split(' ').forEach(option => {
+ // Split the option into key and value
+ // Default the value to `true` if no value
+ let key = '';
+ let value = '';
+ if (option.indexOf('=') !== -1) {
+ [key, value] = option.split('=');
+ } else {
+ key = option;
+ value = true;
+ }
+
+ // Check for version
+ if (key === 'ver') {
+ version = value;
+ } else {
+ // Set the option key to lowercase to make
+ // it easier to query
+ options[key.toLowerCase()] = value;
+ }
+ });
+
+ // No options
+ if (Object.keys(options).length === 0) {
+ // If no other options are set for the
+ // license, set version to 1.0
+ version = '1.0';
+
+ // Set the `zero` option so the link
+ // works correctly
+ options = {
+ zero: true,
+ };
+ }
+
+ // Set the version to whatever was included,
+ // using `defaultVersion` as a fallback if unset
+ version = version || defaultVersion;
+
+ return [licenseType, options, version];
+}
+
+function CourseLicense({
+ license,
+ intl,
+}) {
+ const renderAllRightsReservedLicense = () => (
+
+
+
+ {intl.formatMessage(messages['learn.course.license.allRightsReserved.text'])}
+
+
+ );
+
+ const renderCreativeCommonsLicense = (activeCreativeCommonsLicenseTags, version) => (
+
+
+ {intl.formatMessage(messages['learn.course.license.creativeCommons.terms.preamble'])}
+
+
+ {activeCreativeCommonsLicenseTags.map(tag => (
+
+
+ {intl.formatMessage(messages[CreativeCommonsLicenseTags[tag].intlMessagesId])}
+
+
+
+ ))}
+
+ {intl.formatMessage(messages['learn.course.license.creativeCommons.text'])}
+
+
+ );
+
+ const [licenseType, licenseOptions, licenseVersion] = parseLicense(license);
+
+ return (
+
+ {licenseType === 'all-rights-reserved' && renderAllRightsReservedLicense()}
+ {licenseType === 'creative-commons' && renderCreativeCommonsLicense(
+ Object.keys(licenseOptions),
+ licenseVersion,
+ )}
+
+ );
+}
+
+CourseLicense.propTypes = {
+ license: PropTypes.string,
+ intl: intlShape.isRequired,
+};
+
+CourseLicense.defaultProps = {
+ license: 'all-rights-reserved',
+};
+
+export default injectIntl(CourseLicense);
diff --git a/src/courseware/course/course-license/index.js b/src/courseware/course/course-license/index.js
new file mode 100644
index 00000000..8be76fc6
--- /dev/null
+++ b/src/courseware/course/course-license/index.js
@@ -0,0 +1 @@
+export { default } from './CourseLicense';
diff --git a/src/courseware/course/course-license/messages.js b/src/courseware/course/course-license/messages.js
new file mode 100644
index 00000000..65d6aeda
--- /dev/null
+++ b/src/courseware/course/course-license/messages.js
@@ -0,0 +1,47 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ 'learn.course.license.allRightsReserved.text': {
+ id: 'learn.course.license.allRightsReserved.text',
+ defaultMessage: 'All Rights Reserved',
+ description: 'License text shown when using All Rights Reserved license type.',
+ },
+ 'learn.course.license.creativeCommons.terms.preamble': {
+ id: 'learn.course.license.creativeCommons.terms.preamble',
+ defaultMessage: 'Creative Commons licensed content, with terms as follows:',
+ description: 'Screen reader only text preamble before reading specific Creative Commons license terms.',
+ },
+ 'learn.course.license.creativeCommons.terms.by': {
+ id: 'learn.course.license.creativeCommons.terms.by',
+ defaultMessage: 'Attribution',
+ description: 'Creative Commons license text for Attribution term.',
+ },
+ 'learn.course.license.creativeCommons.terms.nc': {
+ id: 'learn.course.license.creativeCommons.terms.nc',
+ defaultMessage: 'Noncommercial',
+ description: 'Creative Commons license text for Noncommercial term.',
+ },
+ 'learn.course.license.creativeCommons.terms.nd': {
+ id: 'learn.course.license.creativeCommons.terms.nd',
+ defaultMessage: 'No Derivatives',
+ description: 'Creative Commons license text for No Derivatives term.',
+ },
+ 'learn.course.license.creativeCommons.terms.sa': {
+ id: 'learn.course.license.creativeCommons.terms.sa',
+ defaultMessage: 'Share Alike',
+ description: 'Creative Commons license text for Share Alike term.',
+ },
+ // No text for `zero` license
+ 'learn.course.license.creativeCommons.terms.zero': {
+ id: 'learn.course.license.creativeCommons.terms.zero',
+ defaultMessage: 'No terms',
+ description: 'Creative Commons license text for license with no terms.',
+ },
+ 'learn.course.license.creativeCommons.text': {
+ id: 'learn.course.license.creativeCommons.text',
+ defaultMessage: 'Some Rights Reserved',
+ description: 'License text shown when using all Creative Commons license types.',
+ },
+});
+
+export default messages;
diff --git a/src/courseware/course/sequence/Sequence.jsx b/src/courseware/course/sequence/Sequence.jsx
index ccffcd1c..2f1c92c5 100644
--- a/src/courseware/course/sequence/Sequence.jsx
+++ b/src/courseware/course/sequence/Sequence.jsx
@@ -11,6 +11,7 @@ import PageLoading from '../../../generic/PageLoading';
import { UserMessagesContext, ALERT_TYPES } from '../../../generic/user-messages';
import { useModel } from '../../../generic/model-store';
+import CourseLicense from '../course-license';
import messages from './messages';
import { SequenceNavigation, UnitNavigation } from './sequence-navigation';
import SequenceContent from './SequenceContent';
@@ -24,6 +25,7 @@ function Sequence({
previousSequenceHandler,
intl,
}) {
+ const course = useModel('courses', courseId);
const sequence = useModel('sequences', sequenceId);
const unit = useModel('units', unitId);
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
@@ -157,6 +159,9 @@ function Sequence({
)}
+
+
+
);
}
diff --git a/src/courseware/data/__factories__/courseMetadata.factory.js b/src/courseware/data/__factories__/courseMetadata.factory.js
index 44372dba..4d7a5a98 100644
--- a/src/courseware/data/__factories__/courseMetadata.factory.js
+++ b/src/courseware/data/__factories__/courseMetadata.factory.js
@@ -34,6 +34,7 @@ Factory.define('courseMetadata')
},
show_calculator: false,
is_staff: false,
+ license: 'all-rights-reserved',
can_load_courseware: {
has_access: true,
user_fragment: null,
diff --git a/src/courseware/data/api.js b/src/courseware/data/api.js
index ea5bb4b9..838a6a08 100644
--- a/src/courseware/data/api.js
+++ b/src/courseware/data/api.js
@@ -127,6 +127,7 @@ function normalizeMetadata(metadata) {
isEnrolled: metadata.enrollment.is_active,
canLoadCourseware: camelCaseObject(metadata.can_load_courseware),
isStaff: metadata.is_staff,
+ license: metadata.license,
verifiedMode: camelCaseObject(metadata.verified_mode),
tabs: normalizeTabUrls(metadata.id, camelCaseObject(metadata.tabs)),
showCalculator: metadata.show_calculator,
diff --git a/src/index.scss b/src/index.scss
index 0fa2c27f..72bf8194 100755
--- a/src/index.scss
+++ b/src/index.scss
@@ -262,6 +262,10 @@ $primary: #1176B2;
}
}
+.sequence-footer .course-license a:hover {
+ color: theme-color('primary', 500) !important;
+}
+
.unit-container {
padding: 0 $grid-gutter-width 2rem;
max-width: 1024px;