From 3d8c68afb536f27f5b0b3f4f1a18e2033a03d2e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 22 Aug 2018 16:14:51 +0200 Subject: [PATCH] MOBILE-2084 resource: Add filesize and type info --- src/addon/mod/resource/lang/en.json | 3 +- .../mod/resource/providers/module-handler.ts | 74 ++++-- .../components/module/core-course-module.html | 2 +- src/providers/utils/text.ts | 219 ++++++++++++++++++ 4 files changed, 281 insertions(+), 17 deletions(-) diff --git a/src/addon/mod/resource/lang/en.json b/src/addon/mod/resource/lang/en.json index 33c872d40..46a1f7e35 100644 --- a/src/addon/mod/resource/lang/en.json +++ b/src/addon/mod/resource/lang/en.json @@ -1,4 +1,5 @@ { "errorwhileloadingthecontent": "Error while loading the content.", - "openthefile": "Open the file" + "openthefile": "Open the file", + "uploadeddate": "Uploaded {{$a}}" } \ No newline at end of file diff --git a/src/addon/mod/resource/providers/module-handler.ts b/src/addon/mod/resource/providers/module-handler.ts index cfb5066a1..8a9822f33 100644 --- a/src/addon/mod/resource/providers/module-handler.ts +++ b/src/addon/mod/resource/providers/module-handler.ts @@ -14,14 +14,17 @@ import { Injectable } from '@angular/core'; import { NavController, NavOptions } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; import { AddonModResourceProvider } from './resource'; import { AddonModResourceHelperProvider } from './helper'; import { AddonModResourceIndexComponent } from '../components/index/index'; import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreConstants } from '@core/constants'; +import * as moment from 'moment'; /** * Handler to support resource modules. @@ -35,7 +38,8 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler { constructor(protected resourceProvider: AddonModResourceProvider, private courseProvider: CoreCourseProvider, protected mimetypeUtils: CoreMimetypeUtilsProvider, private resourceHelper: AddonModResourceHelperProvider, - protected prefetchDelegate: CoreCourseModulePrefetchDelegate) { + protected prefetchDelegate: CoreCourseModulePrefetchDelegate, protected textUtils: CoreTextUtilsProvider, + protected translate: TranslateService) { } /** @@ -61,7 +65,7 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler { this.resourceHelper.isDisplayedInIframe(module); }; - const handlerData = { + const handlerData: CoreCourseModuleHandlerData = { icon: this.courseProvider.getModuleIconSrc('resource'), title: module.name, class: 'addon-mod_resource-handler', @@ -84,8 +88,10 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler { } ] }; - this.getIcon(module, courseId).then((icon) => { - handlerData.icon = icon; + this.getResourceData(module, courseId).then((data) => { + handlerData.icon = data.icon; + handlerData.extraBadge = data.extra; + handlerData.extraBadgeColor = 'light'; }); this.hideOpenButton(module, courseId).then((hideOpenButton) => { @@ -111,23 +117,61 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler { } /** - * Returns the activity icon. + * Returns the activity icon and data. * * @param {any} module The module object. * @param {number} courseId The course ID. - * @return {Promise} Icon URL. + * @return {Promise} Resource data. */ - protected getIcon(module: any, courseId: number): Promise { - return this.courseProvider.loadModuleContents(module, courseId).then(() => { - if (module.contents.length) { - const filename = module.contents[0].filename, - extension = this.mimetypeUtils.getFileExtension(filename); - if (module.contents.length == 1 || (extension != 'html' && extension != 'htm')) { - return this.mimetypeUtils.getFileIcon(filename); - } + protected getResourceData(module: any, courseId: number): Promise { + return this.resourceProvider.getResourceData(courseId, module.id).then((info) => { + let promise; + + if (info.contentfiles && info.contentfiles.length == 1) { + promise = Promise.resolve(info.contentfiles); + } else { + promise = this.courseProvider.loadModuleContents(module, courseId).then(() => { + if (module.contents.length) { + return module.contents; + } + }); } - return this.courseProvider.getModuleIconSrc('resource'); + return promise.then((files) => { + const resourceData = { + icon: '', + extra: '' + }, + options = this.textUtils.unserialize(info.displayoptions), + extra = []; + + if (files && files.length) { + const file = files[0]; + resourceData.icon = this.mimetypeUtils.getFileIcon(file.filename); + + if (options.showsize) { + const size = files.reduce((result, file) => { + return result + file.filesize; + }, 0); + extra.push(this.textUtils.bytesToSize(size, 1)); + } + if (options.showtype) { + extra.push(this.mimetypeUtils.getMimetypeDescription(file)); + } + } + + if (resourceData.icon == '') { + resourceData.icon = this.courseProvider.getModuleIconSrc('resource'); + } + + if (options.showdate) { + extra.push(this.translate.instant('addon.mod_resource.uploadeddate', + {$a: moment(info.timemodified * 1000).format('LLL')})); + } + resourceData.extra += extra.join(' '); + + return resourceData; + }); }); } diff --git a/src/core/course/components/module/core-course-module.html b/src/core/course/components/module/core-course-module.html index 15a621e17..7780f423f 100644 --- a/src/core/course/components/module/core-course-module.html +++ b/src/core/course/components/module/core-course-module.html @@ -31,7 +31,7 @@
- + {{ 'core.course.hiddenfromstudents' | translate }} diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index 85a749745..4e5ebe842 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -690,4 +690,223 @@ export class CoreTextUtilsProvider { ucFirst(text: string): string { return text.charAt(0).toUpperCase() + text.slice(1); } + + /** + * Unserialize Array from PHP. + * Taken from: https://github.com/kvz/locutus/blob/master/src/php/var/unserialize.js + * + * @param {string} data String to unserialize. + * @param {Function} [logErrorFn] An error to call with the exception to log the error. If not supplied, no error. + * @return {any} Unserialized data. + */ + unserialize (data: string, logErrorFn?: Function): any { + // Discuss at: http://locutus.io/php/unserialize/ + // Original by: Arpad Ray (mailto:arpad@php.net) + // Improved by: Pedro Tainha (http://www.pedrotainha.com) + // Improved by: Kevin van Zonneveld (http://kvz.io) + // Improved by: Kevin van Zonneveld (http://kvz.io) + // Improved by: Chris + // Improved by: James + // Improved by: Le Torbi + // Improved by: Eli Skeggs + // Bugfixed by: dptr1988 + // Bugfixed by: Kevin van Zonneveld (http://kvz.io) + // Bugfixed by: Brett Zamir (http://brett-zamir.me) + // Bugfixed by: philippsimon (https://github.com/philippsimon/) + // Revised by: d3x + // Input by: Brett Zamir (http://brett-zamir.me) + // Input by: Martin (http://www.erlenwiese.de/) + // Input by: kilops + // Input by: Jaroslaw Czarniak + // Input by: lovasoa (https://github.com/lovasoa/) + // Note 1: We feel the main purpose of this function should be + // Note 1: to ease the transport of data between php & js + // Note 1: Aiming for PHP-compatibility, we have to translate objects to arrays + // Example 1: unserialize('a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}') + // Returns 1: ['Kevin', 'van', 'Zonneveld'] + // Example 2: unserialize('a:2:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";}') + // Returns 2: {firstName: 'Kevin', midName: 'van'} + // Example 3: unserialize('a:3:{s:2:"ü";s:2:"ü";s:3:"四";s:3:"四";s:4:"𠜎";s:4:"𠜎";}') + // Returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'} + + const utf8Overhead = (str: string): number => { + let s = str.length; + + for (let i = str.length - 1; i >= 0; i--) { + const code = str.charCodeAt(i); + if (code > 0x7f && code <= 0x7ff) { + s++; + } else if (code > 0x7ff && code <= 0xffff) { + s += 2; + } + // Trail surrogate. + if (code >= 0xDC00 && code <= 0xDFFF) { + i--; + } + } + + return s - 1; + }; + + const error = (type: string, msg: string): void => { + if (logErrorFn) { + logErrorFn(type + msg); + } + }; + + const readUntil = (data: string, offset: number, stopchr: string): Array => { + let i = 2; + const buf = []; + let chr = data.slice(offset, offset + 1); + + while (chr !== stopchr) { + if ((i + offset) > data.length) { + error('Error', 'Invalid'); + } + buf.push(chr); + chr = data.slice(offset + (i - 1), offset + i); + i += 1; + } + + return [buf.length, buf.join('')]; + }; + + const readChrs = (data: string, offset: number, length: number): Array => { + let chr; + const buf = []; + + for (let i = 0; i < length; i++) { + chr = data.slice(offset + (i - 1), offset + i); + buf.push(chr); + length -= utf8Overhead(chr); + } + + return [buf.length, buf.join('')]; + }; + + const _unserialize = (data: string, offset: number): any => { + let dtype, + dataoffset, + keyandchrs, + keys, + contig, + length, + array, + readdata, + readData, + ccount, + stringlength, + i, + key, + kprops, + kchrs, + vprops, + vchrs, + value, + chrs = 0, + typeconvert = (x: any): any => { + return x; + }; + + if (!offset) { + offset = 0; + } + dtype = (data.slice(offset, offset + 1)).toLowerCase(); + + dataoffset = offset + 2; + + switch (dtype) { + case 'i': + typeconvert = (x: any): number => { + return parseInt(x, 10); + }; + readData = readUntil(data, dataoffset, ';'); + chrs = readData[0]; + readdata = readData[1]; + dataoffset += chrs + 1; + break; + case 'b': + typeconvert = (x: any): boolean => { + return parseInt(x, 10) !== 0; + }; + readData = readUntil(data, dataoffset, ';'); + chrs = readData[0]; + readdata = readData[1]; + dataoffset += chrs + 1; + break; + case 'd': + typeconvert = (x: any): number => { + return parseFloat(x); + }; + readData = readUntil(data, dataoffset, ';'); + chrs = readData[0]; + readdata = readData[1]; + dataoffset += chrs + 1; + break; + case 'n': + readdata = null; + break; + case 's': + ccount = readUntil(data, dataoffset, ':'); + chrs = ccount[0]; + stringlength = ccount[1]; + dataoffset += chrs + 2; + + readData = readChrs(data, dataoffset + 1, parseInt(stringlength, 10)); + chrs = readData[0]; + readdata = readData[1]; + dataoffset += chrs + 2; + if (chrs !== parseInt(stringlength, 10) && chrs !== readdata.length) { + error('SyntaxError', 'String length mismatch'); + } + break; + case 'a': + readdata = {}; + + keyandchrs = readUntil(data, dataoffset, ':'); + chrs = keyandchrs[0]; + keys = keyandchrs[1]; + dataoffset += chrs + 2; + + length = parseInt(keys, 10); + contig = true; + + for (let i = 0; i < length; i++) { + kprops = _unserialize(data, dataoffset); + kchrs = kprops[1]; + key = kprops[2]; + dataoffset += kchrs; + + vprops = _unserialize(data, dataoffset); + vchrs = vprops[1]; + value = vprops[2]; + dataoffset += vchrs; + + if (key !== i) { + contig = false; + } + + readdata[key] = value; + } + + if (contig) { + array = new Array(length); + for (i = 0; i < length; i++) { + array[i] = readdata[i]; + } + readdata = array; + } + + dataoffset += 1; + break; + default: + error('SyntaxError', 'Unknown / Unhandled data type(s): ' + dtype); + break; + } + + return [dtype, dataoffset - offset, typeconvert(readdata)]; + }; + + return _unserialize((data + ''), 0)[2]; + } }