From Silly Parakeet, 9 Years ago, written in Plain Text.
Embed
  1. import _filter from 'lodash.filter';
  2. import { CONTENT_CONSTANTS } from './content.constant';
  3. import { CONTRIBUTOR_SOURCE } from './contributorSource.contant';
  4. import { MEDIA_TYPE } from './mediaType.constant';
  5. import { FACET_NAME } from './facetName.constant';
  6.  
  7. class Content {
  8.     constructor(content) {
  9.         const restPath = ContentFactory.Paths.get('REST');
  10.         angular.extend(this, content || {});
  11.         this.contentItems = (this.contentItems || []).map(item => new Content(item));
  12.  
  13.         if (this.associatedTools && this.associatedTools.contentItems) {
  14.             this.associatedTools.contentItems = this.associatedTools.contentItems.map(item => new Content(item));
  15.         }
  16.  
  17.         if (this.associatedTeacherSupport && this.associatedTeacherSupport.contentItems) {
  18.             this.associatedTeacherSupport.contentItems = this.associatedTeacherSupport.contentItems
  19.                 .map(item => new Content(item));
  20.         }
  21.  
  22.         if (!this.metrics) {
  23.             this.metrics = {
  24.                 uuid: null,
  25.                 version: 1,
  26.                 visitCount: 0,
  27.                 commentCount: 0,
  28.             };
  29.         }
  30.         this.buildFromDetailsTree();
  31.     }
  32.  
  33.     static query(params, less, cache) {
  34.         ContentFactory.$log.log('Content.query called', params);
  35.  
  36.         const promise = ContentFactory.api.get(less ? 'items' : 'content', {
  37.             params,
  38.             config: { cache: !!cache },
  39.         })
  40.         .then((response) => {
  41.             response.data.results = Content.getListWithCustomizedTestInserted(response.data.results);
  42.             return response;
  43.         })
  44.         .then(convertQueryResults)
  45.         .then(updateQueryResultsTeacherOnly)
  46.         .then(result => (params.STATE_STANDARD ? processStandardDescription(result) : result));
  47.  
  48.         return promise;
  49.     }
  50.  
  51.     static queryFast(params, success, error) {
  52.         return ContentFactory.api.get('searchFast', {
  53.             params,
  54.         })
  55.         .then((response) => {
  56.             const results = [];
  57.  
  58.             angular.forEach(response.data, (item) => {
  59.                 results.push(new Content(item));
  60.             });
  61.             success(results);
  62.  
  63.             return results;
  64.         }, error);
  65.     }
  66.  
  67.     static getETexts(productIdQuery, cache) {
  68.         return ContentFactory.api.get('searchFast', {
  69.             params: {
  70.                 OLE_PRODUCT_ID: productIdQuery,
  71.                 pageSize: '20',
  72.                 MEDIA_TYPE: MEDIA_TYPE.ETEXT,
  73.                 NOT_ITEM_STATUS: [CONTENT_CONSTANTS.NOT_ITEM_STATUS.DELETED,
  74.                     CONTENT_CONSTANTS.NOT_ITEM_STATUS.ARCHIVED],
  75.             },
  76.             config: { cache: !!cache },
  77.         }).then((response) => {
  78.             const results = [];
  79.  
  80.             response.data.forEach((etext) => {
  81.                 results.push(new Content(etext));
  82.             });
  83.  
  84.             return results;
  85.         }, err => ContentFactory.$q.reject(err));
  86.     }
  87.  
  88.     static getToolsByPrograms(productIds, cache) {
  89.         const promise = ContentFactory.api.get('tools', {
  90.             params: {
  91.                 programs: productIds,
  92.             },
  93.             config: { cache: !!cache },
  94.         }).then((response) => {
  95.             const tools = [];
  96.             response.data.forEach((tool) => {
  97.                 tools.push(new Content(tool));
  98.             });
  99.             return tools;
  100.         }, err => ContentFactory.$q.reject(err));
  101.  
  102.         return promise;
  103.     }
  104.  
  105.     static setStudentCenterData(params) {
  106.         let promise = null;
  107.  
  108.         promise = ContentFactory.api.post('studentCenters', params)
  109.             .then(response => response.data);
  110.  
  111.         return promise;
  112.     }
  113.  
  114.     /**
  115.      * @param Boolean less
  116.      * when true, call does not return empty or null fields of items
  117.      * Note: get a single content item
  118.      */
  119.     static get(params, less, cache, skipInsertCustomized) {
  120.         let future = {};
  121.         let promise = null;
  122.         const paramsClone = Object.assign(params);
  123.  
  124.         if (angular.isUndefined(paramsClone.levels)) {
  125.             paramsClone.levels = 1;
  126.         }
  127.  
  128.         promise = ContentFactory.api.get(less ? 'singleItem' : 'singleContent', {
  129.             params: paramsClone,
  130.             config: { cache: !!cache },
  131.         })
  132.         .then((response) => {
  133.             processStandardDescription(response.data);
  134.             return response;
  135.         })
  136.         .then((response) => {
  137.             if (!skipInsertCustomized) {
  138.                 const isCustomizedSequence = response.data.fileType === CONTENT_CONSTANTS.FILE_TYPE.SEQUENCE &&
  139.                     Content.hasCustomizedItem(response.data);
  140.                 if (isCustomizedSequence) {
  141.                     response.data.customizedItem.contentItems =
  142.                         Content
  143.                         .getListWithCustomizedTestInserted(response.data.customizedItem.contentItems);
  144.                 }
  145.                 response.data.contentItems =
  146.                     Content.getListWithCustomizedTestInserted(response.data.contentItems);
  147.             }
  148.             return response;
  149.         })
  150.         .then((response) => {
  151.             future = angular.assign({}, new Content(response.data));
  152.  
  153.             if (future.fileType === CONTENT_CONSTANTS.FILE_TYPE.SEQUENCE) {
  154.                 const filterFunction = item => item.externalSource === CONTENT_CONSTANTS.EXTERNALSOURCE.OPEN_ED;
  155.                 return OpenEdItem.retrieveItemsInList(future.contentItems, filterFunction)
  156.                     .then(() => future.updateTeacherOnly());
  157.             } else if (future.externalSource === CONTENT_CONSTANTS.EXTERNALSOURCE.OPEN_ED) {
  158.                 const originalEquellaItemId = future.id;
  159.                 return OpenEdItem.getPromise(future.externalId).then((item) => {
  160.                     future = new Content(item);
  161.                     future.id = originalEquellaItemId;
  162.                     return future.updateTeacherOnly();
  163.                 });
  164.             }
  165.             return future.updateTeacherOnly();
  166.         });
  167.  
  168.         return promise;
  169.     }
  170.  
  171.     static getWithoutInsertingCustomized(params, less, cache) {
  172.         const skipInsertCustomized = true;
  173.         return Content.get(params, less, cache, skipInsertCustomized);
  174.     }
  175.  
  176.     static hasCustomizedItem(item) {
  177.         return item.customizedItem && item.customizedItemDefaultView;
  178.     }
  179.  
  180.     static isHiddenViaCustomize(item) {
  181.         return item.associativeProps && item.associativeProps.externalSource ===
  182.             CONTENT_CONSTANTS.EXTERNALSOURCE.HIDE_FROM_STUDENT;
  183.     }
  184.  
  185.     hasAssociatedItem(name) {
  186.         const item = this;
  187.         const key = getJsonKeyForAssociatedItem(name);
  188.  
  189.         return item[key] &&
  190.             !!item[key].id &&
  191.             !!item[key].version;
  192.     }
  193.  
  194.     setAssociatedItem(name, less, cache) {
  195.         const item = this;
  196.         const key = getJsonKeyForAssociatedItem(name);
  197.  
  198.         const deferredAssociatedItem = ContentFactory.$q.defer();
  199.  
  200.         if (item.hasAssociatedItem(name)) {
  201.             Content.get({
  202.                 contentId: item[key].id,
  203.                 version: item[key].version,
  204.             }, less, cache).then((associatedItem) => {
  205.                 item[key] = associatedItem;
  206.                 deferredAssociatedItem.resolve();
  207.             }, () => {
  208.                 ContentFactory.$q.reject();
  209.             });
  210.         } else {
  211.             deferredAssociatedItem.resolve();
  212.         }
  213.         return deferredAssociatedItem.promise;
  214.     }
  215.  
  216.     static getRoute(item, program, tier, lesson) {
  217.         /** in this method "tier" may refer to an actual Tier or a Lesson
  218.          * we need all this logic JUST so we can have a little arrow that says back to whatever
  219.          */
  220.         if (program && tier && lesson) {
  221.             /**
  222.              * this case there are no nested lessons, so tier must be a tier, and item must be general content
  223.              */
  224.             return `/program/${program.id}/${program.version}/tier/${tier.id}/${tier.version}/lesson/${lesson.id}
  225.                 /${lesson.version}/content/${item.id}/${item.version}`;
  226.         } else if (program && tier) {
  227.             if (item.editAssessment) {
  228.                 return `/program/${program.id}/${program.version}/${tier.mediaType.toLowerCase()}/
  229.                     ${tier.id}/${tier.version}/assessment/${item.id}/${item.version}/edit`;
  230.             }
  231.             if (item.mediaType === MEDIA_TYPE.LESSON) {
  232.                 return `/program/${program.id}/${program.version}/
  233.                     ${tier.mediaType.toLowerCase()}/${tier.id}/${tier.version}/lesson/${item.id}/${item.version}`;
  234.             }
  235.             return `/program/${program.id}/${program.version}/
  236.                 ${tier.mediaType.toLowerCase()}/${tier.id}/${tier.version}/content/${item.id}/${item.version}`;
  237.         } else if (program) {
  238.             if (item.editAssessment) {
  239.                 return `/program/${program.id}/${program.version}/assessment/${item.id}/${item.version}/edit`;
  240.             }
  241.             const tierDelim = item.mediaType === MEDIA_TYPE.LESSON ||
  242.                 item.mediaType === MEDIA_TYPE.TIER ? item.mediaType.toLowerCase() : 'content';
  243.             return `/program/${program.id}/${program.version}/${tierDelim}/${item.id}/${item.version}`;
  244.         } else if (item.essayPrompt) {
  245.             return `/assessment/essay/${item.id}/${item.version}/edit`;
  246.         } else if (item.mediaType === MEDIA_TYPE.TEST) {
  247.             return `/assessment/${item.id}/${item.version}/edit`;
  248.         } else if (item.mediaType === MEDIA_TYPE.TIER && item.root) {
  249.             return `/program/${item.id}/${item.version}`;
  250.         } else if (item.mediaType === MEDIA_TYPE.LESSON) {
  251.             return `/lesson/${item.id}/${item.version}/edit`;
  252.         }
  253.  
  254.         return `/content/${item.id}/${item.version}`;
  255.     }
  256.  
  257.     static publish(contentItem, callback) {
  258.         ContentFactory.api.put('publishedContent',
  259.             {
  260.                 params: {
  261.                     contentItemId: contentItem.id,
  262.                     contentItemVersion: contentItem.version,
  263.                 },
  264.             },
  265.         )
  266.         .success((response) => {
  267.             const allProgramsUrl = `${restPath}/programs;
  268.             const allCentersUrl = ${restPath}/centers;
  269.             const cache = ContentFactory.$cacheFactory.get('$http');
  270.  
  271.             cache.remove(allProgramsUrl);
  272.             cache.remove(allCentersUrl);
  273.  
  274.             if (response) {
  275.                 callback();
  276.             }
  277.         });
  278.     }
  279.  
  280.     static getAssessment(aId, success) {
  281.         return ContentFactory.api.get('assessment', {
  282.             params: { assessmentId: aId + '.json' },
  283.         })
  284.         .then(setSkillsLocaleDescription)
  285.         .success(success)
  286.         .error((data) => {
  287.             ContentFactory.$log.error('failed to retrieve assessment', data.errorCode, data.errorMessage);
  288.         });
  289.     }
  290.  
  291.     static getSkillsById(ids, success) {
  292.         return ContentFactory.api.get('skills', {
  293.             params: {
  294.                 'ids[]': ids,
  295.             },
  296.         })
  297.         .success(success)
  298.         .error((data) => {
  299.             ContentFactory.$log.error('failed to fetch skill details', data.errorCode, data.errorMessage);
  300.         });
  301.     }
  302.  
  303.     static updateMultiValueFacets(params) {
  304.         const paramsClone = Object.assign({}, params);
  305.         const facetNames = [FACET_NAME.FEATURED_RESOURCES, FACET_NAME.GRADE, FACET_NAME.LEARNING_MODEL,
  306.             FACET_NAME.LIBRARY_TITLE, FACET_NAME.SUBTOPIC, FACET_NAME.COMPREHENSION_SKILLS, FACET_NAME.TEXT_FEATURES,
  307.             FACET_NAME.GENRES, FACET_NAME.CONTENT_AREAS];
  308.         angular.forEach(facetNames, (name) => {
  309.             if (paramsClone[name] && paramsClone[name].length !== 0) {
  310.                 const joinedValues = paramsClone[name].join(',');
  311.                 params[name] = `ALL(${joinedValues})`;
  312.             }
  313.         });
  314.     }
  315.  
  316.     static getLessonTOCRoute(programId, programVersion, UUID) {
  317.         return `/program/${programId}/${programVersion}/tier/${UUID}/0`;
  318.     }
  319.  
  320.     static getTeacherOnlyValues(itemUuids, success) {
  321.         ContentFactory.$log.log('getTeacherOnlyValues called', itemUuids);
  322.         const promise = ContentFactory.api.get('teacherOnly', {
  323.             params: {
  324.                 itemUuids,
  325.             },
  326.             config: { cache: true },
  327.         });
  328.  
  329.         promise.success((response) => {
  330.             ContentFactory.$log.log('getTeacherOnlyValues success', response);
  331.         })
  332.         .error((data) => {
  333.             ContentFactory.$log.error('failed to fetch teacherOnly values', data.errorCode, data.errorMessage);
  334.         });
  335.  
  336.         if (angular.isDefined(success) && angular.isFunction(success)) {
  337.             promise.success(success);
  338.         }
  339.  
  340.         return promise;
  341.     }
  342.  
  343.     static getContentStandards(id, version) {
  344.         const deferred = ContentFactory.$q.defer();
  345.  
  346.         Content.get({
  347.             contentId: id,
  348.             version,
  349.         }, false, true)
  350.         .then((response) => {
  351.             const program = response;
  352.             program.libraryDelimited = program.library.join('|');
  353.  
  354.             Standard.getStandardTree(program.libraryDelimited, program)
  355.                 .then((tree) => {
  356.                     deferred.resolve({
  357.                         program,
  358.                         standards: tree,
  359.                     });
  360.                 },
  361.                 deferred.reject,
  362.             );
  363.         },
  364.             deferred.reject,
  365.         );
  366.  
  367.         return deferred.promise;
  368.     }
  369.  
  370.     static getQuestionBankStandards(id, version) {
  371.         const deferred = ContentFactory.$q.defer();
  372.  
  373.         Content.get({
  374.             contentId: id,
  375.             version,
  376.         }, false, true)
  377.         .then((response) => {
  378.             const program = response;
  379.             program.libraryDelimited = program.library.join('|');
  380.  
  381.             Standard.getQuestionBankStandardTree(program.libraryDelimited, program)
  382.                 .then((tree) => {
  383.                     deferred.resolve({
  384.                         program,
  385.                         standards: tree,
  386.                     });
  387.                 },
  388.                 deferred.reject,
  389.             );
  390.         },
  391.             deferred.reject,
  392.         );
  393.  
  394.         return deferred.promise;
  395.     }
  396.  
  397.     static hasRemediation(item) {
  398.         return item.hasRemediation();
  399.     }
  400.  
  401.     static getListWithCustomizedTestInserted(contentArray) {
  402.         const resultArray = [];
  403.  
  404.         angular.forEach(contentArray, (contentItem) => {
  405.             const contentItemClone = Object.assign(contentItem);
  406.             if (contentItemClone.mediaType === MEDIA_TYPE.TEST && Content.hasCustomizedItem(contentItemClone) &&
  407.                 !contentItemClone.hasCustomizedVersion) {
  408.                 contentItemClone.hasCustomizedVersion = true;
  409.                 resultArray.push(contentItemClone);
  410.  
  411.                 const customizedItem = contentItemClone.customizedItem;
  412.                 customizedItem.isCustomizedVersion = true;
  413.                 const originalCopy = angular.assign({}, contentItemClone);
  414.                 delete originalCopy.customizedItem;
  415.                 customizedItem.originalItem = originalCopy;
  416.                 resultArray.push(customizedItem);
  417.             } else if (contentItemClone.fileType === MEDIA_TYPE.SEQUENCE && contentItemClone.contentItems &&
  418.                 contentItemClone.contentItems.length > 0) {
  419.                 const childItems = contentItemClone.contentItems;
  420.                 contentItemClone.contentItems = Content.getListWithCustomizedTestInserted(childItems);
  421.                 resultArray.push(contentItemClone);
  422.             } else {
  423.                 resultArray.push(contentItemClone);
  424.             }
  425.         });
  426.         return resultArray;
  427.     }
  428.  
  429.     isLastRow(index, contentItemsLength, sidebarOpen) {
  430.         let columnCount;
  431.  
  432.         if (sidebarOpen && !ContentFactory.MediaQuery.breakpoint.isDesktop) {
  433.             columnCount = 2;
  434.         } else if (sidebarOpen || !ContentFactory.MediaQuery.breakpoint.isDesktop) {
  435.             columnCount = 3;
  436.         } else {
  437.             columnCount = 4;
  438.         }
  439.  
  440.         const lastRowItemCount = contentItemsLength % columnCount;
  441.  
  442.         if (lastRowItemCount === 0) {
  443.             return (index + 1) > (contentItemsLength - columnCount);
  444.         }
  445.         return (index + 1) > (contentItemsLength - lastRowItemCount);
  446.     }
  447.  
  448.     updateTeacherOnly() {
  449.         const self = this;
  450.         const unknownIds = [];
  451.         let yesCount = 0;
  452.         const deferred = ContentFactory.$q.defer();
  453.         let i;
  454.         let len;
  455.  
  456.         if (self.contentItems && self.contentItems.length > 0) {
  457.             for (i = 0, len = self.contentItems.length; i < len; i += 1) {
  458.                 const child = self.contentItems[i];
  459.  
  460.                 if (!child.contentItems && child.teacherOnly === CONTENT_CONSTANTS.TEACHER_ONLY.IS_TEACHER_ONLY) {
  461.                     if (self.teacherOnly !== CONTENT_CONSTANTS.TEACHER_ONLY.IS_TEACHER_ONLY) {
  462.                         self.teacherOnly = 'No';
  463.                     }
  464.                 }
  465.                 if (child.teacherOnly.toUpperCase() === CONTENT_CONSTANTS.TEACHER_ONLY.UNKNOWN) {
  466.                     unknownIds.push(child.id);
  467.                 }
  468.                 if (child.teacherOnly.toUpperCase() === CONTENT_CONSTANTS.TEACHER_ONLY.IS_TEACHER_ONLY) {
  469.                     yesCount += 1;
  470.                 }
  471.             }
  472.             if (yesCount === self.contentItems.length) {
  473.                 self.teacherOnly = 'Yes';
  474.             }
  475.             if (unknownIds.length > 0) {
  476.                 Content.getTeacherOnlyValues(unknownIds)
  477.                     .success((values) => {
  478.                         Object.keys(values).map((key) => {
  479.                             let item = self.contentItems;
  480.                             item.find(contentItem => contentItem.id === value);
  481.                             item = item.length ? item[0] : null;
  482.                             if (item !== null) {
  483.                                 item.teacherOnly = values[key] ? 'Yes' : 'No';
  484.                             }
  485.                             return key;
  486.                         });
  487.                         deferred.resolve(self);
  488.                     });
  489.             } else {
  490.                 deferred.resolve(self);
  491.             }
  492.         } else if (self.teacherOnly === CONTENT_CONSTANTS.TEACHER_ONLY.UNKNOWN) {
  493.             Content.getTeacherOnlyValues([self.id])
  494.                 .success((values) => {
  495.                     self.teacherOnly = values[self.id] ? 'Yes' : 'No';
  496.                     deferred.resolve(self);
  497.                 });
  498.         } else {
  499.             deferred.resolve(self);
  500.         }
  501.         return deferred.promise;
  502.     }
  503.  
  504.     /**
  505.      * Get the title for an item.  If an active customized version exists, will fetch that title instead.
  506.      *
  507.      */
  508.     getTitle(forceVersion) {
  509.         let item;
  510.         let title;
  511.         const forceOriginal = forceVersion === 'original';
  512.         const forceLatest = forceVersion === 'latest';
  513.  
  514.         if (forceLatest) {
  515.             item = this.getDefaultVersion(forceLatest);
  516.         } else {
  517.             item = forceOriginal ? this : this.getDefaultVersion();
  518.         }
  519.  
  520.         if (item.associativeProps && item.associativeProps.titleInSequence) {
  521.             title = item.associativeProps.titleInSequence;
  522.         } else {
  523.             title = item.title;
  524.         }
  525.         return title;
  526.     }
  527.  
  528.     getDescription() {
  529.         const item = this.getDefaultVersion();
  530.         return item.text;
  531.     }
  532.  
  533.     getEquellaItemId() {
  534.         return this.id;
  535.     }
  536.  
  537.     getDefaultVersion(forceLatest) {
  538.         const item = this;
  539.         const hasCustomizedItem = Content.hasCustomizedItem(item);
  540.  
  541.         if (!forceLatest && this.isTest() && !this.myContent) {
  542.             return item;
  543.         }
  544.         return hasCustomizedItem ? new Content(item.customizedItem) : item;
  545.     }
  546.  
  547.     getUrl() {
  548.         let url = this.url;
  549.  
  550.         if (!(/^(http|https):\/\//.test(url))) {
  551.             url = `http://${url}`;
  552.         }
  553.         if (this.mediaType === MEDIA_TYPE.PARTNER_LINK) {
  554.             url = `rest/externaltool/content/view/${this.id}/${this.version}?launchUrl=${url}`;
  555.             if (ContentFactory.$rootScope.currentProgram) {
  556.                 url = `${url}&programId=${ContentFactory.$rootScope.currentProgram.id}
  557.                     &programVersion=${ContentFactory.$rootScope.currentProgram.version}`;
  558.             }
  559.         }
  560.         return url;
  561.     }
  562.  
  563.     hasRemediation() {
  564.         const item = this;
  565.  
  566.         return !!item.getDefaultVersion().remediationProperties &&
  567.             (item.fileType === CONTENT_CONSTANTS.FILE_TYPE.TEST || item.fileType === CONTENT_CONSTANTS.FILE_TYPE.SCO ||
  568.              item.fileType === CONTENT_CONSTANTS.FILE_TYPE.TIN_CAN_SCO);
  569.     }
  570.  
  571.     hasRemediationActivities() {
  572.         const item = this;
  573.         return item.remediationSkills && !ContentFactory.ObjectUtilities.isEmpty(item.remediationSkills);
  574.     }
  575.  
  576.     hasInfo() {
  577.         const cleanTags = [];
  578.         angular.forEach(this.tags, (tag) => {
  579.             if (tag.trim().length > 0) {
  580.                 cleanTags.push(tag);
  581.             }
  582.         });
  583.         this.tags = cleanTags;
  584.  
  585.         if (this.fileType === CONTENT_CONSTANTS.FILE_TYPE.ETEXT) {
  586.             return this.text.trim().length > 0;
  587.         }
  588.  
  589.         const infoItemHasContent = (infoItem) => {
  590.             const isEmptyArray = angular.isArray(infoItem) && (infoItem.join('').trim().length === 0);
  591.             const isEmptyString = angular.isString(infoItem) && infoItem.trim().length === 0;
  592.             if (isEmptyArray || isEmptyString || !infoItem) {
  593.                 return false;
  594.             }
  595.             return true;
  596.         };
  597.  
  598.         const item = this;
  599.  
  600.         const canHazText = infoItemHasContent(item.text);
  601.  
  602.         const canHazTags = infoItemHasContent(item.tags);
  603.         const canHazMaterials = infoItemHasContent(item.materials);
  604.         const canHazStandards = infoItemHasContent(item.standards);
  605.         const canHazPacing = infoItemHasContent(item.pacing);
  606.         const canHazAuthor = infoItemHasContent(item.author);
  607.         const canHazIsbn = infoItemHasContent(item.isbn);
  608.         const canHazLexile = infoItemHasContent(item.lexile);
  609.         const canHazGR = infoItemHasContent(item.guidedReading);
  610.         const canHazRMM = infoItemHasContent(item.readingMaturityMetric);
  611.         const canHazDRA = infoItemHasContent(item.developmentalReadingAssessment);
  612.         const canHazQuantile = infoItemHasContent(item.quantile);
  613.         const canHazCompSkills = infoItemHasContent(item.comprehensionSkills);
  614.         const canHazTextFeatures = infoItemHasContent(item.textFeatures);
  615.         const canHazGenres = infoItemHasContent(item.genres);
  616.         const canHazContentAreas = infoItemHasContent(item.contentAreas);
  617.         const isStudent = ContentFactory.$rootScope.currentUser.isStudent;
  618.  
  619.         return canHazText || canHazTags || canHazStandards ||
  620.             (canHazMaterials && !isStudent) ||
  621.             (canHazPacing && !isStudent) ||
  622.             (canHazAuthor && !isStudent) ||
  623.             (canHazIsbn && !isStudent) ||
  624.             (canHazLexile && !isStudent) ||
  625.             (canHazGR && !isStudent) ||
  626.             (canHazRMM && !isStudent) ||
  627.             (canHazDRA && !isStudent) ||
  628.             (canHazQuantile && !isStudent) ||
  629.             (canHazCompSkills && !isStudent) ||
  630.             (canHazTextFeatures && !isStudent) ||
  631.             (canHazGenres && !isStudent) ||
  632.             (canHazContentAreas && !isStudent);
  633.     }
  634.  
  635.     isCustomized() {
  636.         const item = this;
  637.  
  638.         return item.customizedItem && item.customizedItemDefaultView;
  639.     }
  640.  
  641.     getCustomizeContribDate() {
  642.         const item = this;
  643.         return item.getDefaultVersion().contribDate;
  644.     }
  645.  
  646.     isPearsonItem() {
  647.         const self = this;
  648.         return self.contribSource === CONTRIBUTOR_SOURCE.PEARSON;
  649.     }
  650.  
  651.     isAssignable() {
  652.         const item = this;
  653.         if (item.teacherOnly === CONTENT_CONSTANTS.TEACHER_ONLY.UNKNOWN) {
  654.             ContentFactory.$log.log('Unresolved unknown teacherOnly', item);
  655.         }
  656.         return ContentFactory.$rootScope.currentUser.hasRole('ROLE_TEACHER') &&
  657.             item.teacherOnly === CONTENT_CONSTANTS.TEACHER_ONLY.NOT_TEACHER_ONLY &&
  658.             item.mediaType !== MEDIA_TYPE.TIER &&
  659.             item.mediaType !== MEDIA_TYPE.CENTER;
  660.     }
  661.  
  662.     isPublishable() {
  663.         const item = this;
  664.  
  665.         return ContentFactory.$rootScope.currentUser.isLibraryAdmin && !item.isCustomized();
  666.     }
  667.  
  668.     isExternalResource() {
  669.         const self = this;
  670.         return self.isOpenEdItem() || self.isGooruItem();
  671.     }
  672.  
  673.     getThumbnailUrl(page, showLarge) {
  674.         const item = this;
  675.  
  676.         if (item.isOpenEdItem()) {
  677.             return item.thumbnailUrls.length ? item.thumbnailUrls[0] : undefined;
  678.         }
  679.  
  680.         if ((!item.thumbnailLocation && item.root) || (!item.thumbnailUrls && !item.root)) {
  681.             return undefined;
  682.         }
  683.  
  684.         let thumbnailPath = '';
  685.         let thumbnailName = '';
  686.         let thumbnailUrl = '';
  687.         let expectedThumbnailName = '';
  688.         let retinaThumbnailName = '';
  689.         let nonRetinaThumbnailName = '';
  690.         let fallbackThumbnailName = '';
  691.         let uxPath = '';
  692.         let retinaExtension = '';
  693.         const defaultRetina = '@2x';
  694.         let thumbnailExtension = '';
  695.         const defaultExtension = '.png';
  696.         const pagePosition = {
  697.             HOME: 'HOME',
  698.             PROGRAM_SUBNAV: 'PROGRAM_SUBNAV',
  699.             TIER: 'TIER',
  700.         };
  701.  
  702.         if (ContentFactory.browserInfo.isHDDisplay) {
  703.             retinaExtension = '@2x';
  704.         }
  705.  
  706.         if (item.root) {
  707.             const isFullPathToThumbnail = item.thumbnailLocation.search(/^http(s)?|\//) === 0;
  708.             if (!isFullPathToThumbnail) {
  709.                 item.thumbnailLocation = `${ContentFactory.Paths.get('SHARED_THUMBNAILS')}/${item.thumbnailLocation}`;
  710.             }
  711.  
  712.             thumbnailPath = ContentFactory.$filter('removeExtension')(item.thumbnailLocation);
  713.  
  714.             if (page === pagePosition.HOME) {
  715.                 uxPath = (showLarge) ? '_homelarge' : '_homesmall';
  716.             } else if (page === pagePosition.PROGRAM_SUBNAV') {
  717.                 uxPath = '_dropdown';
  718.             } else {
  719.                 uxPath = '_course';
  720.             }
  721.  
  722.             thumbnailExtension = ContentFactory.$filter('getExtension')(item.thumbnailLocation, defaultExtension);
  723.  
  724.             return thumbnailPath + uxPath + retinaExtension + thumbnailExtension;
  725.         }
  726.         if (page === pagePosition.TIER) {
  727.             if (showLarge) {
  728.                 uxPath = '_grid';
  729.             }
  730.         }
  731.         thumbnailName = ContentFactory.$filter('getFileName')(item.thumbnailUrls[0]);
  732.         thumbnailExtension = ContentFactory.$filter('getExtension')(item.thumbnailUrls[0], defaultExtension);
  733.         expectedThumbnailName = thumbnailName + uxPath + retinaExtension + thumbnailExtension;
  734.         retinaThumbnailName = thumbnailName + uxPath + defaultRetina + thumbnailExtension;
  735.         nonRetinaThumbnailName = thumbnailName + uxPath + thumbnailExtension;
  736.         thumbnailUrl = item.thumbnailUrls.find(url => url.search(expectedThumbnailName) > -1);
  737.         if (!thumbnailUrl) {
  738.             fallbackThumbnailName = ContentFactory.browserInfo.isHDDisplay ? nonRetinaThumbnailName :
  739.                 retinaThumbnailName;
  740.  
  741.             thumbnailUrl = item.thumbnailUrls.find(url => url.search(fallbackThumbnailName) > -1);
  742.         }
  743.  
  744.         return thumbnailUrl;
  745.     }
  746.  
  747.     isOpenEdItem() {
  748.         return isExternalItem(CONTENT_CONSTANTS.FILE_TYPE.OPEN_ED, this);
  749.     }
  750.  
  751.     getValidFromDetails() {
  752.         const item = this;
  753.         if (!item.fromDetailsTrees) {
  754.             return [];
  755.         }
  756.         return item.fromDetailsTrees.filter(fromDetail =>
  757.             fromDetail.distanceFrom === 1 && (fromDetail.containerMediaType === 'Program' ||
  758.             fromDetail.containerMediaType === 'Tier' || fromDetail.containerMediaType === 'Lesson' ||
  759.             fromDetail.containerMediaType === 'Learning Model'));
  760.     }
  761.  
  762.     buildFromDetailsTree() {
  763.         const self = this;
  764.         const trees = [];
  765.         let currentTree = null;
  766.         let workingTree = null;
  767.         let lastDistance = 100;
  768.         const details = self.fromDetails;
  769.  
  770.         angular.forEach(details, (detail) => {
  771.             const contentDetail = Object.assign(detail);
  772.             if (contentDetail.containerMediaType === 'Tier' && contentDetail.root) {
  773.                 contentDetail.containerMediaType = 'Program';
  774.             }
  775.  
  776.             if (contentDetail.distanceFrom <= lastDistance) {
  777.                 if (lastDistance !== 100) {
  778.                     currentTree.path = tierSplicer(currentTree.path);
  779.                     trees.push(currentTree);
  780.                 }
  781.                 currentTree = angular.assign({}, contentDetail);
  782.                 workingTree = currentTree;
  783.                 if (currentTree.containerMediaType !== 'Learning Model') {
  784.                     currentTree.treeTitle = currentTree.containerTitle;
  785.                     currentTree.path = [
  786.                         currentTree.containerMediaType.toLowerCase(),
  787.                         currentTree.parentItemUuid,
  788.                         0,
  789.                     ].join('/');
  790.                 } else {
  791.                     currentTree.treeTitle = '';
  792.                     currentTree.path = '';
  793.                 }
  794.             } else {
  795.                 workingTree.parent = angular.assign({}, contentDetail);
  796.                 workingTree = workingTree.parent;
  797.                 if (workingTree.containerMediaType !== 'Learning Model') {
  798.                     if (currentTree.treeTitle === '') {
  799.                         currentTree.treeTitle = workingTree.containerTitle;
  800.                         currentTree.path = [
  801.                             workingTree.containerMediaType.toLowerCase(),
  802.                             workingTree.parentItemUuid,
  803.                             0,
  804.                         ].join('/');
  805.                     } else {
  806.                         currentTree.treeTitle = `${currentTree.treeTitle} : ${workingTree.containerTitle}`;
  807.                         currentTree.path = [
  808.                             workingTree.containerMediaType.toLowerCase(),
  809.                             workingTree.parentItemUuid,
  810.                             0,
  811.                             currentTree.path,
  812.                         ].join('/');
  813.                     }
  814.                 }
  815.             }
  816.             lastDistance = contentDetail.distanceFrom;
  817.         });
  818.  
  819.         if (currentTree) {
  820.             currentTree.path = tierSplicer(currentTree.path);
  821.             trees.push(currentTree);
  822.         }
  823.  
  824.         self.fromDetailsTrees = trees;
  825.         self.fromDetailsForSearch = self.getValidFromDetails();
  826.     }
  827.  
  828.     propagateAssociativeProps(parent) {
  829.         const item = this;
  830.         const associativePropsReady = ContentFactory.$q.defer();
  831.  
  832.         if (ContentFactory.$rootScope.openedItem && ContentFactory.$rootScope.openedItem.id === item.id) {
  833.             item.associativeProps = ContentFactory.$rootScope.openedItem.associativeProps;
  834.             associativePropsReady.resolve();
  835.         } else if (!parent.id || !parent.version) {
  836.             associativePropsReady.resolve();
  837.         } else {
  838.             Content.get({
  839.                 contentId: parent.id,
  840.                 version: parent.version,
  841.                 levels: 1,
  842.             }, true).then((fullParentData) => {
  843.                 const itemInParentContext =
  844.                     fullParentData.contentItems.find(contentItem => contentItems.id === item.id);
  845.  
  846.                 item.associativeProps = itemInParentContext ? itemInParentContext.associativeProps :
  847.                     null;
  848.                 associativePropsReady.resolve();
  849.             });
  850.         }
  851.  
  852.         return associativePropsReady.promise;
  853.     }
  854.  
  855.     isScoOrTest() {
  856.         return this.fileType === 'SCO' || this.fileType === 'TEST' ||
  857.         this.fileType === CONTENT_CONSTANTS.FILE_TYPE.TIN_CAN_SCO;
  858.     }
  859.  
  860.     isScorable() {
  861.         return this.isScoOrTest();
  862.     }
  863.  
  864.     isReviewerContainer() {
  865.         if (this.externalSource) {
  866.             return this.externalSource.toUpperCase() === 'STANDARD';
  867.         }
  868.  
  869.         return false;
  870.     }
  871.  
  872.     isLesson() {
  873.         return this.mediaType === 'Lesson';
  874.     }
  875.  
  876.     isRRS() {
  877.         return this.mediaType === MEDIA_TYPE.REALIZE_READER_SELECTION;
  878.     }
  879.  
  880.     hasCustomizedLesson() {
  881.         return this.isLesson() && this.contribSource === CONTRIBUTOR_SOURCE.PEARSON &&
  882.             this.customizedItemDefaultView;
  883.     }
  884.  
  885.     isTest() {
  886.         return this.mediaType === 'Test';
  887.     }
  888.  
  889.     hasCustomizedTest() {
  890.         return !!(this.isTest() && this.customizedItem && this.hasCustomizedVersion);
  891.     }
  892.  
  893.     isCustomizedTest() {
  894.         return !!(this.isTest() && this.isCustomizedVersion);
  895.     }
  896.  
  897.     isStudentVoice() {
  898.         return this.mediaType === 'Student Voice';
  899.     }
  900.  
  901.     isTestNavTest() {
  902.         return this.isTest() && this.playerTarget === 'testnav';
  903.     }
  904.  
  905.     isNativeTest() {
  906.         return this.isTest() && (this.playerTarget === 'realize' || this.playerTarget === '');
  907.     }
  908.  
  909.     isDiscussionPrompt() {
  910.         return this.mediaType === MEDIA_TYPE.DISCUSSION_PROMPT;
  911.     }
  912.  
  913.     getAssignedStatus() {
  914.         return ContentFactory.api.get('assignmentStatus', { params: { id: this.id } })
  915.             .then(response => response.data.replace(/"/g, ''),
  916.             err => ContentFactory.$q.reject('error getting item assigned status', err));
  917.     }
  918.  
  919.     isAssigned() {
  920.         const promise = this.getAssignedStatus().then(status => status === 'ASSIGNED');
  921.         return promise;
  922.     }
  923.  
  924.     isCenterProgram() {
  925.         const self = this;
  926.         return !!(self.root && self.centersProperties && self.centersProperties.itemUuid !== null);
  927.     }
  928.  
  929.     isGooruItem() {
  930.         return !!this.gooruOid || isExternalItem(CONTENT_CONSTANTS.FILE_TYPE.GOORU, this);
  931.     }
  932.  
  933.     getExternalResourceDefaultImage() {
  934.         if (isExternalItem(CONTENT_CONSTANTS.FILE_TYPE.OPEN_ED, this)) {
  935.             return '/opened_thumbnail_default.png';
  936.         } else if (isExternalItem(CONTENT_CONSTANTS.FILE_TYPE.GOORU, this)) {
  937.             return '/gooru_icon.png';
  938.         }
  939.         return '';
  940.     }
  941.  
  942.     getLessonItems() {
  943.         const self = this;
  944.         const childContents = [];
  945.  
  946.         angular.forEach(self.contentItems, (level1) => {
  947.             if (level1.mediaType !== MEDIA_TYPE.LEARNING_MODEL) {
  948.                 childContents.push(level1);
  949.             } else {
  950.                 angular.forEach(level1.contentItems, (item) => {
  951.                     const itemClone = Object.assign(itemClone);
  952.                     itemClone.fromLearningModel = true;
  953.                     childContents.push(itemClone);
  954.                 });
  955.             }
  956.         });
  957.         return childContents;
  958.     }
  959.  
  960.     getRRSActivities() {
  961.         let RRSActivities = [];
  962.  
  963.         return this.getItemContainer(MEDIA_TYPE.ACTIVITY_SUPPORT)
  964.             .then((response) => {
  965.                 const responseObj = response.data;
  966.                 if (responseObj && responseObj.contentItems && responseObj.contentItems.length > 0) {
  967.                     RRSActivities = responseObj.contentItems;
  968.                 }
  969.                 this.RRSActivities = RRSActivities;
  970.                 return RRSActivities;
  971.             }, (error) => {
  972.                 ContentFactory.$log.error('Failed to get activity support container:', error);
  973.                 return error;
  974.             });
  975.     }
  976.  
  977.     getItemContainer(type) {
  978.         return ContentFactory.api.get('itemContainer', {
  979.             params: {
  980.                 mediaType: type,
  981.             },
  982.         });
  983.     }
  984.  
  985.     isLtiItem() {
  986.         return this.mediaType === 'Partner Link';
  987.     }
  988.  
  989.     static getLtiLaunchInfo(contentId, contentVersion, optionalParams) {
  990.         const params = optionalParams;
  991.         params.contentId = contentId;
  992.         params.contentVersion = contentVersion;
  993.         return ContentFactory.api.get('externalTool', params).then((response) => {
  994.             response.data.settings.openNewWindow = angular.fromJson(response.data.settings.openNewWindow);
  995.             return response.data;
  996.         });
  997.     }
  998.  
  999.     getClassroomShareUrl() {
  1000.         const deferred = ContentFactory.$q.defer();
  1001.         const item = this.getDefaultVersion();
  1002.         const path = ContentFactory.$location.absUrl();
  1003.         const target = item.isLesson() ? 'lesson' : 'content';
  1004.         const contentPath = [path, target, item.id, item.version].join('/');
  1005.         let details;
  1006.         function prepareShareUrl(ssoUrlDetails) {
  1007.             if (ssoUrlDetails.idpDataUrl) {
  1008.                 return `${ssoUrlDetails.rumbaLoginUrl}?service=${contentPath}&idpMetadata=${ssoUrlDetails.idpDataUrl}`;
  1009.             }
  1010.             return `${ssoUrlDetails.rumbaLoginUrl}?service=${contentPath}`;
  1011.         }
  1012.  
  1013.         if (isSSOUrlDetailsAvailable()) {
  1014.             details = ContentFactory.$currentUser.getAttribute('rumba.ssourldetails');
  1015.             deferred.resolve(prepareShareUrl(details));
  1016.         } else {
  1017.             ContentFactory.User.getSSOUrlDetails()
  1018.                 .then((sslDetails) => {
  1019.                     if (sslDetails) {
  1020.                         ContentFactory.$currentUser.setAttribute('rumba.ssourldetails', {
  1021.                             rumbaLoginUrl: sslDetails.rumbaLoginUrl,
  1022.                             idpDataUrl: sslDetails.idpDataUrl,
  1023.                         }, false);
  1024.                         return deferred.resolve(prepareShareUrl(sslDetails));
  1025.                     }
  1026.                     return {};
  1027.                 }, (error) => {
  1028.                     deferred.reject(error);
  1029.                 });
  1030.         }
  1031.  
  1032.         return deferred.promise;
  1033.     }
  1034.  
  1035.     isClassroomSharable() {
  1036.         const item = this;
  1037.         const isContainerType = (item.fileType === CONTENT_CONSTANTS.FILE_TYPE.SEQUENCE);
  1038.  
  1039.         if (!ContentFactory.$currentUser.isGoogleClassroomEnabled() ||
  1040.             item.teacherOnly === 'Yes' || item.hideFromStudent ||
  1041.             (isContainerType && !item.isLesson())) {
  1042.             return false;
  1043.         }
  1044.         return item.mediaType !== MEDIA_TYPE.DISCUSSION_PROMPT &&
  1045.             item.mediaType !== MEDIA_TYPE.ADAPTIVE_HOMEWORK &&
  1046.             item.mediaType !== MEDIA_TYPE.LEVELED_READER &&
  1047.             item.mediaType !== MEDIA_TYPE.PARTNER_LINK &&
  1048.             item.mediaType !== MEDIA_TYPE.LEARNING_MODEL &&
  1049.             item.mediaType !== MEDIA_TYPE.STUDENT_VOICE &&
  1050.             item.mediaType !== MEDIA_TYPE.QUESTION_BANK &&
  1051.             item.mediaType !== MEDIA_TYPE.ACTIVITY_SUPPORT;
  1052.     }
  1053. }
  1054.  
  1055. function isExternalItem(externalSource, self) {
  1056.     if (!self.fileType) {
  1057.         return false;
  1058.     }
  1059.     const externalSourceLowerCase = angular.lowercase(externalSource);
  1060.  
  1061.     const contributor = angular.lowercase(self.contribSource);
  1062.     const source = angular.lowercase(self.externalSource);
  1063.  
  1064.     return contributor === externalSourceLowerCase || source === externalSourceLowerCase;
  1065. }
  1066.  
  1067. function skillsLocaleDescription(skill) {
  1068.     const skillInfo = Object.assign(skill);
  1069.     if ((ContentFactory.$rootScope.currentProgram.language === 'Spanish' ||
  1070.             ContentFactory.$rootScope.currentUser.getAttribute('profile.locale') === 'es') &&
  1071.         (skillInfo.spanishDescription !== '' && skillInfo.spanishDescription !== null)) {
  1072.         skillInfo.description = skillInfo.spanishDescription;
  1073.     }
  1074.     return skillInfo;
  1075. }
  1076.  
  1077. function setSkillsLocaleDescription(response) {
  1078.     const results = [];
  1079.     angular.forEach(response.data.questions, (question) => {
  1080.         question.skillDetails.map(skill => skillsLocaleDescription(skill));
  1081.         results.push(question);
  1082.     });
  1083.     return angular.extend({}, response.data, {
  1084.         results,
  1085.     });
  1086. }
  1087.  
  1088. function convertQueryResults(response) {
  1089.     const results = [];
  1090.  
  1091.     angular.forEach(response.data.results, (item) => {
  1092.         results.push(new Content(item));
  1093.     });
  1094.  
  1095.     return OpenEdItem.retrieveItemsInList(results, item => item.externalSource === 'OpenEd')
  1096.         .then((allResults) => {
  1097.             const next = angular.assign({}, response.data);
  1098.             next.results = allResults;
  1099.             return next;
  1100.         });
  1101. }
  1102.  
  1103. function updateQueryResultsTeacherOnly(response) {
  1104.     const ids = ContentFactory.arrayUtilities.pluck(response.results.filter(result =>
  1105.         result.mediaType === MEDIA_TYPE.Lesson && result.teacherOnly === 'UNKNOWN'), 'id');
  1106.     let promise;
  1107.  
  1108.     if (ids.length > 0) {
  1109.         promise = Content.getTeacherOnlyValues(ids)
  1110.         .then((teacherOnlyResponse) => {
  1111.             let i;
  1112.             let len;
  1113.             Object.keys(teacherOnlyResponse.data).forEach((key) => {
  1114.                 for (i = 0, len = response.results.length; i < len; i += 1) {
  1115.                     if (response.results[i].id === key) {
  1116.                         response.results[i].teacherOnly = teacherOnlyResponse.data[key] ?
  1117.                             'Yes' : 'No';
  1118.                         break;
  1119.                     }
  1120.                 }
  1121.             });
  1122.  
  1123.             const next = angular.assign({}, response);
  1124.             return next;
  1125.         });
  1126.  
  1127.         return promise;
  1128.     }
  1129.     ContentFactory.$log.log('content.query : no unknowns to test');
  1130.  
  1131.     const next = angular.assign({}, response);
  1132.     return next;
  1133. }
  1134.  
  1135. function getJsonKeyForAssociatedItem(name) {
  1136.     switch (name) {
  1137.     case 'Teacher Support':
  1138.         return 'associatedTeacherSupport';
  1139.     case 'Tools':
  1140.         return 'associatedTools';
  1141.     default:
  1142.         return '';
  1143.     }
  1144. }
  1145.  
  1146. function tierSplicer(path) {
  1147.     const r = /tier/g;
  1148.     let match = null;
  1149.     let idx = 0;
  1150.     let count = 0;
  1151.     let result = path;
  1152.  
  1153.     match = r.exec(path);
  1154.     while (match) {
  1155.         count += 1;
  1156.         if (count > 1) {
  1157.             idx = match.index + 4 + (count - 2);
  1158.             result = result.slice(0, idx) + count + result.slice(idx);
  1159.         }
  1160.         match = r.exec(path);
  1161.     }
  1162.  
  1163.     return result;
  1164. }
  1165.  
  1166. function isSSOUrlDetailsAvailable() {
  1167.     return ContentFactory.$currentUser.getAttribute('rumba.ssourldetails');
  1168. }
  1169.  
  1170. export function ContentFactory($filter, browserInfo, Paths, $log, $q, $cacheFactory, mediaQuery, objectUtilities,
  1171.     $rootScope, arrayUtilities, $currentUser, $location, User, api) {
  1172.     ContentFactory.$filter = $filter;
  1173.     ContentFactory.browserInfo = browserInfo;
  1174.     ContentFactory.Paths = Paths;
  1175.     ContentFactory.$log = $log;
  1176.     ContentFactory.$q = $q;
  1177.     ContentFactory.$cacheFactory = $cacheFactory;
  1178.     ContentFactory.MediaQuery = mediaQuery;
  1179.     ContentFactory.ObjectUtilities = objectUtilities;
  1180.     ContentFactory.$rootScope = $rootScope;
  1181.     ContentFactory.arrayUtilities = arrayUtilities;
  1182.     ContentFactory.$currentUser = $currentUser;
  1183.     ContentFactory.$location = $location;
  1184.     ContentFactory.User = User;
  1185.     ContentFactory.api = api;
  1186.     return Content;
  1187. }
  1188.  
  1189. export default ContentFactory;
  1190.