def __init__(self, id=None):
     super(SCO, self).__init__(id)
     # annotations = IAnnotations(self)
     # try:
     #     TRACK_KEY = self.UID()
     #     print 'TRACK_KEY',TRACK_KEY
     # except:
     #     TRACK_KEY = 'eduintelligent.sco.track'
     # self.track = annotations.setdefault(TRACK_KEY, TrackingStorage())
     self.track = TrackingStorage()
 def __init__(self, id=None):
     super(SCO, self).__init__(id)
     # annotations = IAnnotations(self)
     # try:
     #     TRACK_KEY = self.UID()
     #     print 'TRACK_KEY',TRACK_KEY
     # except:
     #     TRACK_KEY = 'eduintelligent.sco.track'
     # self.track = annotations.setdefault(TRACK_KEY, TrackingStorage())
     self.track = TrackingStorage()
 def __init__(self, id=None):
     super(Evaluation, self).__init__(id)
     self.track = TrackingStorage()
class Evaluation(folder.ATFolder):
    """Contains multiple questions.
    """
    implements(IEvaluation)
    _at_rename_after_creation = True

    def __init__(self, id=None):
        super(Evaluation, self).__init__(id)
        self.track = TrackingStorage()
        #self.scormAPI = IScormAPI(self.track)

    def initTrack(self):
        if not hasattr(self, TRACK_ATTRIBUTE):
            setattr(self, TRACK_ATTRIBUTE, TrackingStorage())
        #if not hasattr(self, 'scormAPI'):    
        #    self.scormAPI = IScormAPI(self.track)

    def getNumberUserQuestion(self):
        questions = self.getNumberOfRandomQuestions()

        if questions > 0:
            return questions

        return len(self.objectIds())

    def generateQuestions(self):
        randomize = self.isRandomOrder()
        limit = self.getNumberOfRandomQuestions()

        questions = self.objectIds()
        #print "query results",results
        #questions = list(results)
        if randomize:
            #questions = list(results)
            questions.sort(lambda x,y: cmp(random.randint(0,200),100))

        if limit > 0:
            questions = questions[:limit]

        return questions


    cmi_interactions = ('id', 'type', 'time', 'weighting', 'student_response','result', 'latency', 'comments')

    def generateEvaluation(self):
        data = {}
        for i,q in enumerate(self.generateQuestions()):
            for key in self.cmi_interactions:
                keyId = "question.%s.%s"%(i,key)
                data[keyId] = ''
                if key == 'id':
                    data[keyId] = q
        data['evaluation.open'] = 0
        data['evaluation.scored'] = 0
        data['evaluation.score'] = 0
        data['evaluation.scored_extra'] = False
        data['evaluation.score_extra'] = 0.0
        data['evaluation.start'] = time.time()
        data['evaluation.end'] = 0
        print "generateEvaluation",data
        return data


    def getEvaluation(self, userId=None):
        """ 
        returns the track's runId 
        if is None the user don't have more opportunities
        """
        taskId = self.getId()
        if not userId:
            userId = self.AuthenticatedMember()

        track = self.track.getLastUserTrack(taskId,0,userId)

        if track and not track.data['evaluation.end'] and not track.data['evaluation.scored']:
            return track.data

        # revisar el numero de oportunidades
        # si el # de oportunidades es mmenor o igual no generar nada    
        data = self.generateEvaluation()
        self.track.saveUserTrack(taskId, 0, userId, data)
        return data

    def getEvaluationDetails(self, userId=None, numeval=None):

        if not userId or not numeval:
            return {},[]
        data_eval = {}
        data_interactions = []
        tracks = self.track.getUserTracks(self.getId(), 0, userId)
        if not tracks:
            return {},[]

        num = int(numeval) - 1
        evaluation = sorted(tracks, key=lambda x: x.timeStamp)[num]
        correct_responses = 0
        #print evaluation.data
        data_eval['start'] = self.utilConvertTime(evaluation.data['evaluation.start'])
        data_eval['end'] = self.utilConvertTime(evaluation.data['evaluation.end'])
        data_eval['period'] = self.utilCalculeTime(evaluation.data['evaluation.start'], evaluation.data['evaluation.end'])
        data_eval['score'] = evaluation.data['evaluation.score']
        data_eval['scored'] = evaluation.data['evaluation.scored']
        data_eval['open'] = evaluation.data['evaluation.open']
        data_eval['total_questions'] = (len(evaluation.data)-5)/8
        for e in range(data_eval['total_questions']):
            tmp = {}
            tmp['index'] = e + 1
            tmp['type'] = evaluation.data['question.%s.type'%e]
            tmp['time'] = self.utilConvertTimeHM(evaluation.data['question.%s.time'%e])
            tmp['latency'] = evaluation.data['question.%s.latency'%e]
            tmp['result'] = evaluation.data['question.%s.result'%e]
            if tmp['result']:
                correct_responses += 1
            tmp['weighting'] = evaluation.data['question.%s.weighting'%e]
            tmp['comments'] = evaluation.data['question.%s.comments'%e]

            obj = self._getOb(evaluation.data['question.%s.id'%e])
            tmp['question'] = obj.Title()

            tmp['student_response'] = evaluation.data['question.%s.student_response'%e]

            if tmp['type'] == 'choice':
                if not tmp['student_response']:
                    tmp['student_response'] = _("You did not respond within the time of question")
                    tmp['correct_response'] = ''
                else:
                    tmp['student_response'] = ', '.join(obj.getAnswerTitles(evaluation.data['question.%s.student_response'%e]))
                    tmp['correct_response'] = ', '.join(obj.getCorrectAnswerTitles())

            data_interactions.append(tmp)

        data_eval['correct_responses'] = correct_responses
        data_eval['evaluation.score_extra'] = evaluation.data['evaluation.score_extra']
        data_eval['evaluation.scored_extra'] = evaluation.data['evaluation.scored_extra']

        return data_eval,data_interactions

    def getScoreStatus(self, scored=None, score=None):
        if not scored:
            return _("Pending")

        if score < self.getMinScoreGrade():
            return _("Unapproved")

        return _("Approved")


    def getLastEvaluation(self, userId=None):
        taskId = self.getId()

        if not userId:
            userId = self.AuthenticatedMember()
        
        track = self.track.getLastUserTrack(taskId,0,userId)
        if track:
            return track.data
        return {}


    def saveUserResponse(self, data ,userId=None):
        taskId = self.getId()

        if not userId:
            userId = self.AuthenticatedMember()

        track = self.track.getLastUserTrack(taskId, 0, userId)
        if track is not None:
            return self.track.updateTrack(track, data)
        #self.track.saveUserTrack(taskId, 0, userId, data, update=True)


    def getDataUserId(self, runId, userId=None):
        if not userId:
            userId = self.AuthenticatedMember()

        track = self.track.getLastUserTrack(self.getId(), runId, userId)
        #print " getDataUserId ",track.data
        return track.data

    def getDataEvaluations(self, userId=None):
        if not userId:
            userId = self.AuthenticatedMember()
        tracks = self.track.getUserTracks(self.getId(), 0, userId)
        if tracks:
            ### Note: se tuvo que poner estas lineas para correguir el error de que un examen no tenga datos completos
            ###       esto sucede cuando un usuario inicia un examen dos veces el la mima hora y no ha terminado uno
            # for track in tracks:
            #     if len(track.data) < len(self.generateEvaluation()):
            #         return []
            return sorted(tracks, key=lambda x: x.timeStamp)
        return []

    def calculateScore(self, data):
        keys = len(data) # 
        interactions = (keys-5)/8  # 5 is evaluation data and 8 is the key of one interaction
        total_weigthing = self.getTotalWerighting(data)
        correct_answers = 0
        for e in range(interactions):
            result = data['question.%s.result'%e]
            if result:
                weighting = data['question.%s.weighting'%e]
                correct_answers += weighting        

        score_ratio = round((float(correct_answers)*100.0)/total_weigthing,2)
        return score_ratio

    def getTotalWerighting(self, data):
        return sum([self._getOb(data[i]).getWeighting() for i in data.keys() if i.endswith('.id')])            

    def formatTimeLeft(self, userId=None):
        if self.getMaxTimeResponseTest() <= 0 : return ''
        if not userId:
            userId = self.AuthenticatedMember()

        last = self.track.getLastUserTrack(self.getId(), 0, userId)
        testInfo = last.data.get('evaluation.start',time.time())

        secondsPassed = time.time() - testInfo
        secondsLeft = self.getMaxTimeResponseTest() * 60 - secondsPassed

        seconds = secondsLeft % 60
        minutes = (secondsLeft / 60) % 60
        hours   = secondsLeft / 3600

        return "%02d:%02d:%02d" % (hours, minutes, seconds)

    def getAllDataUsers(self):
        result = []
        tracks = sorted(self.track.values(),key=lambda x: x.userName)
        for track in tracks:
            tmp = {}
            tmp['userid'] = track.userName
            tmp['score'] = track.data['evaluation.score']
            tmp['start'] = self.utilConvertTime(track.data['evaluation.start'])
            tmp['end'] = self.utilConvertTime(track.data['evaluation.end'])
            tmp['time'] = self.utilCalculeTime(track.data['evaluation.start'], track.data['evaluation.end'])
            tmp['member'] = self.AuthenticatedMemberById(tmp['userid'])
            result.append(tmp)
        return result

    def getGroupDataUsers(self):

        results = []

        for k, g in groupby(self.track.values(), key=lambda r: r.userName):
            tmp = {}
            tmp['userid'] = k

            average = 0
            count = 0.0
                
            for data in g:
                #Let's fake data. This is quite, quite wrong!!!
                if not data.data.get('evaluation.scored_extra'):
                    scored_extra = False
                    score_extra = 0.0
                else:
                    scored_extra = data.data['evaluation.scored_extra']
                    score_extra = data.data['evaluation.score_extra']
                #Ends bad,dirty,evil hack

                if data.data['evaluation.scored']:
                    average += data.data['evaluation.score']
                    count += 1.0

            if not count:
                count = 1.0

            tmp['average'] = round(average/count,2)
            tmp['oportunities'] = range(1,len(g)+1)
            tmp['member'] = self.AuthenticatedMemberById(k)
            last_track = sorted(g, key=lambda x: x.timeStamp)[-1]
            tmp['score'] = last_track.data['evaluation.score']
            tmp['score_extra'] = score_extra
            tmp['scored_extra'] = score_extra
            tmp['start'] = self.utilConvertTime(last_track.data['evaluation.start'])
            tmp['end'] = self.utilConvertTime(last_track.data['evaluation.end'])
            tmp['time'] = self.utilCalculeTime(last_track.data['evaluation.start'], last_track.data['evaluation.end'])


            results.append(tmp)

        return results

    def getPendingGradeUsers(self):
        result = []
        tracks = sorted(self.track.values(),key=lambda x: x.userName)
        for track in tracks:
            if not track.data['evaluation.scored'] and track.data['evaluation.open']:            
                tmp = {}
                tmp['userid'] = track.userName
                tmp['score'] = track.data['evaluation.score']
                tmp['start'] = self.utilConvertTime(track.data['evaluation.start'])
                tmp['end'] = self.utilConvertTime(track.data['evaluation.end'])
                tmp['time'] = self.utilCalculeTime(track.data['evaluation.start'], track.data['evaluation.end'])
                tmp['member'] = self.AuthenticatedMemberById(tmp['userid'])
                result.append(tmp)
        return result

    def getOpenEvaluation(self, userId=None):
        results = []
        evaluation = self.getLastEvaluation(userId)
        total_questions = (len(evaluation)-5)/8
        for e in range(total_questions):
            if evaluation['question.%s.type'%e] == 'fill-in':
                question = {}
                question['index'] = e
                question['student_response'] = evaluation['question.%s.student_response'%e]
                question['time'] = self.utilConvertTimeHM(evaluation['question.%s.time'%e])
                question['weighting'] = evaluation['question.%s.weighting'%e]
                question['latency'] = evaluation['question.%s.latency'%e]
                qid = evaluation['question.%s.id'%e]
                question['title'] = self._getOb(qid).Title()

                result = evaluation['question.%s.result'%e]

                if result == '':
                    results.append(question)

        return results

    def setOpenEvaluation(self, data, userId=None):
        evaluation = self.getLastEvaluation(userId)
        self.saveUserResponse(data)
        pass

    def getPendingUsers(self):
        users_evaluation = [x['userid'] for x in self.getGroupDataUsers()]
        parent = self.aq_inner.aq_parent     ## verificar que es un ExamContent
        parent = parent.aq_inner.aq_parent   ## verificar que es un Course
        users_course = parent.getRegisteredStudents()
        a = frozenset(users_course)
        b = frozenset(users_evaluation)
        c = a - b
        result = []
        for user in list(c):
            member = self.AuthenticatedMemberById(user)
            result.append(member)
        return result

    def utilCalculeTime(self, time1, time2):
        if not time1 or not time2:
            return "0"

        time1 = int(time1)
        time2 = int(time2)
        totalseconds = time2 - time1
        seconds = totalseconds % 60
        minutes = (totalseconds / 60) % 60
        hours   = totalseconds / 3600
        return "%02d:%02d:%02d" % (hours, minutes, seconds)

    def utilConvertTime(self, time1):
        if not time1:
            return "0"
        time1 = time.localtime(float(time1))
        time1 = time.strftime('%d/%m/%Y - %H:%M', time1)
        return time1

    def utilConvertTimeHM(self, time1):
        if time1 == '':
            time1 = 0.0
        n = float(time1)/1000.0
        time1 = time.localtime(n)
        time1 = time.strftime('%H:%M:%S', time1)
        return time1


    def haveOpportunity(self):
        # si sus evaluacions es menor al permitido
        # si el examen no esta vencido
        if len(self.getDataEvaluations()) < self.getMaxOpportunityTest():
            return True
        return False

    def haveTime(self):
        now            = DateTime()
        startPublished = self.getInitDate()
        endPublished   = self.getFinishDate()
        if((startPublished != None) and (startPublished > now)):
            return False
        if((endPublished != None) and (now > endPublished)):
            return False
        return True

    def passEvaluation(self, userId=None):
        data = self.getLastEvaluation(userId)
        if not data:
            return False

        if data['evaluation.open'] and not data['evaluation.scored']:
            return True

        if data['evaluation.score'] < self.getMinScoreGrade():
            return False

        return True

    def AuthenticatedMember(self):
        portal_membership = getToolByName(self, 'portal_membership')
        return portal_membership.getAuthenticatedMember().getId()

    def AuthenticatedMemberById(self, userId=None):
        portal_membership = getToolByName(self, 'portal_membership')

        if not userId:
            return portal_membership.getAuthenticatedMember()

        member = portal_membership.getMemberById(userId)

        # Hack temporal que corrige error cuando un usuario es eliminado
        if not member:
            class Member:
                def getFullname(self):
                    return '--'
                def getPositionName(self):
                    return '--'
                def getPlace(self):
                    return '--'
                def getProductName(self):
                    return ['--',]
                def getDivisionName(self):
                    return ['--',]
                def getDistrict(self):
                    return '--'
                def getRegion(self):
                    return '--'
                def getEmployee(self):
                    return '--'
                def getIngress(self):
                    return '--'
                def getCountryName(self):
                    return '--'
                def getState(self):
                    return '--'
                def getCity(self):
                    return '--'
                def getPlace(self):
                    return '--'

            member = Member()

        return member

    def sendMessage(self, data=None, isOpen=False, userId=None):
        manager = getUtility(IMessagesManager)
        receiver = sender = self.AuthenticatedMember()
        if userId:
            receiver = userId
        subject = _(u"Exam name: ") + self.Title()
        normal = _(u"Your grade is: ") + str(data['evaluation.score'])
        openbody = _(u"The instructor has reviewed your exam and your grade was ")  + str(data['evaluation.score'])
        link = _(u"To see the details of this revision, please follow this link: ") + "<a href=%s target=_blank> %s </a>"%(self.absolute_url(),self.Title())
        body = normal + link
        if isOpen:
            body = openbody + link

        manager.message_new(2, sender, receiver, subject, body)

    def setScoreKardex(self, data, userId=None):
        ## validate if the user is a eduMember or has kardex attribute
        member = self.AuthenticatedMemberById(userId)
        end = self.utilConvertTime(data['evaluation.end'])
        member.setDynamicKardex(evaluation=self.getEvaluationNameAndLink(),
                                date=end,
                                course=self.getCourseName(),
                                score=str(data['evaluation.score']),
                                type='exam')

        pass

    def getCourseName(self):
        parent = self.aq_inner.aq_parent     ## verificar que es un ExamContent
        parent = parent.aq_inner.aq_parent   ## verificar que es un Course
        return parent.Title()

    def getEvaluationNameAndLink(self):
        return self.Title() + '|' + self.absolute_url()

    #############
    # Statistics
    #############
    def getExamStatistics(self, attemp=0):
        sample = []
        for userid, g in groupby(self.track.values(), key=lambda r: r.userName):
            ## !!!! check if have more than one for filter the attemp
            data = sorted(g, key=lambda x: x.timeStamp)[attemp] # filter the attemp

            if data.data['evaluation.scored']:
                sample.append(data.data['evaluation.score'])

        if not sample:
            sample = [0]
            
        return Statistics(sample)


    def getQuestionsStatistics(self, attemp=0):
        """
        obtener todas las preguntas del examen
        crear diccionario con qid, question, correct, incorrect
        agregarlo a una lista

        """
        catalog = getToolByName(self, 'portal_catalog')
        return [ dict(qid=question.getId,
                      title=question.Title,
                      counts=self.getCountUserQuestions(question.getId, attemp=attemp))
                 for question in 
                 catalog(path=dict(query='/'.join(self.getPhysicalPath()),
                                   depth=1),)
                 ]


    def getCountUserQuestions(self, qid, attemp=0):
        correct = []
        incorrect = []

        for userid, g in groupby(self.track.values(), key=lambda r: r.userName):
            ## !!!! check if have more than one for filter the attemp
            data = sorted(g, key=lambda x: x.timeStamp)[attemp] # filter the attemp

            keys = len(data.data) # 
            interactions = (keys-5)/8  # 5 is evaluation data and 8 is the key of one interaction
            for e in range(interactions):
                if data.data['question.%s.id'%e] == qid:
                    result = data.data['question.%s.result'%e]
                    if result:
                        correct.append(userid)
                    else:
                        incorrect.append(userid)
                    break

        return (correct,incorrect)

    
    def getQuestionTitle(self, qid):
        return self._getOb(qid).Title()

    ########
    #  Module dependency
    ########        

    def passEvaluation(self, userId=None):
        taskId = self.getId()

        if not userId:
            userId = self.AuthenticatedMember()

        track = self.track.getLastUserTrack(taskId,0,userId)
        if track:
            score = track.data['evaluation.score']
            return score > self.getMinScoreGrade()
        return False


    def canTakeExam(self, userId=None):
        """
        """
        # comprobar si tiene asociado un examen

        if not userId:
            userId = self.AuthenticatedMember()

        evaluation = self.getEvaluationDependecy()
        if evaluation:
            return evaluation.passEvaluation(userId)
        return True

    def getPublishState(self):
        """
        """
        wftool = getToolByName(self, "portal_workflow")
        return wftool.getInfoFor(self, 'review_state')
 def initTrack(self):
     if not hasattr(self, TRACK_ATTRIBUTE):
         setattr(self, TRACK_ATTRIBUTE, TrackingStorage())
 def __init__(self, evid=None):
     #super(Evaluation, self).__init__(evid)
     folder.ATFolder.__init__(self, evid)
     
     self.track = TrackingStorage()
class Evaluation(folder.ATFolder):
    """Contains multiple questions.
    """
    implements(IEvaluation)
    _at_rename_after_creation = True

    def __init__(self, evid=None):
        #super(Evaluation, self).__init__(evid)
        folder.ATFolder.__init__(self, evid)
        
        self.track = TrackingStorage()
        #self.scormAPI = IScormAPI(self.track)

    def initTrack(self):
        if not hasattr(self, TRACK_ATTRIBUTE):
            setattr(self, TRACK_ATTRIBUTE, TrackingStorage())
        #if not hasattr(self, 'scormAPI'):    
        #    self.scormAPI = IScormAPI(self.track)

    def getNumberUserQuestion(self):
        #
        questions = self.getNumberOfRandomQuestions()

        if questions > 0:
            return questions

        return len(self.objectIds())

    def generateQuestions(self):
        randomize = self.isRandomOrder()
        limit = self.getNumberOfRandomQuestions()

        questions = self.objectIds()
        #print "query results",results
        #questions = list(results)
        if randomize:
            #questions = list(results)
            questions.sort(lambda x,y: cmp(random.randint(0,200),100))

        if limit > 0:
            questions = questions[:limit]

        return questions


    cmi_interactions = ('id', 'type', 'time', 'weighting', 'student_response','result', 'latency', 'comments')

    def generateEvaluation(self):
        data = {}
        for i,q in enumerate(self.generateQuestions()):
            for key in self.cmi_interactions:
                keyId = "question.%s.%s"%(i,key)
                data[keyId] = ''
                if key == 'id':
                    data[keyId] = q
        data['evaluation.open'] = 0
        data['evaluation.scored'] = 0
        data['evaluation.score'] = 0
        data['evaluation.scored_extra'] = False
        data['evaluation.score_extra'] = 0.0
        data['evaluation.start'] = time.time()
        data['evaluation.end'] = 0
        print "generateEvaluation",data
        return data


    def getEvaluation(self, userId=None):
        """ 
        returns the track's runId 
        if is None the user don't have more opportunities
        """
        taskId = self.getId()
        if not userId:
            userId = self.AuthenticatedMember()

        track = self.track.getLastUserTrack(taskId,0,userId)

        if track and not track.data['evaluation.end'] and not track.data['evaluation.scored']:
            return track.data

        # revisar el numero de oportunidades
        # si el # de oportunidades es mmenor o igual no generar nada    
        data = self.generateEvaluation()
        self.track.saveUserTrack(taskId, 0, userId, data)
        return data

    def getEvaluationDetails(self, userId=None, numeval=None):

        if not userId or not numeval:
            return {},[]
        data_eval = {}
        data_interactions = []
        tracks = self.track.getUserTracks(self.getId(), 0, userId)
        if not tracks:
            return {},[]

        num = int(numeval) - 1
        evaluation = sorted(tracks, key=lambda x: x.timeStamp)[num]
        correct_responses = 0
        #print evaluation.data
        data_eval['start'] = self.utilConvertTime(evaluation.data['evaluation.start'])
        data_eval['end'] = self.utilConvertTime(evaluation.data['evaluation.end'])
        data_eval['period'] = self.utilCalculeTime(evaluation.data['evaluation.start'], evaluation.data['evaluation.end'])
        data_eval['score'] = evaluation.data['evaluation.score']
        data_eval['scored'] = evaluation.data['evaluation.scored']
        data_eval['open'] = evaluation.data['evaluation.open']
        data_eval['total_questions'] = (len(evaluation.data)-5)/8
        for e in range(data_eval['total_questions']):
            tmp = {}
            tmp['index'] = e + 1
            tmp['type'] = evaluation.data['question.%s.type'%e]
            tmp['time'] = self.utilConvertTimeHM(evaluation.data['question.%s.time'%e])
            tmp['latency'] = evaluation.data['question.%s.latency'%e]
            tmp['result'] = evaluation.data['question.%s.result'%e]
            if tmp['result']:
                correct_responses += 1
            tmp['weighting'] = evaluation.data['question.%s.weighting'%e]
            tmp['comments'] = evaluation.data['question.%s.comments'%e]

            obj = self._getOb(evaluation.data['question.%s.id'%e])
            tmp['question'] = obj.Title()

            tmp['student_response'] = evaluation.data['question.%s.student_response'%e]

            if tmp['type'] == 'choice':
                if not tmp['student_response']:
                    tmp['student_response'] = _("You did not respond within the time of question")
                    tmp['correct_response'] = ''
                else:
                    tmp['student_response'] = ', '.join(obj.getAnswerTitles(evaluation.data['question.%s.student_response'%e]))
                    tmp['correct_response'] = ', '.join(obj.getCorrectAnswerTitles())

            data_interactions.append(tmp)

        data_eval['correct_responses'] = correct_responses
        data_eval['evaluation.score_extra'] = evaluation.data['evaluation.score_extra']
        data_eval['evaluation.scored_extra'] = evaluation.data['evaluation.scored_extra']

        return data_eval,data_interactions

    def getScoreStatus(self, scored=None, score=None):
        if not scored:
            return _("Pending")

        if score < self.getMinScoreGrade():
            return _("Unapproved")

        return _("Approved")


    def getLastEvaluation(self, userId=None):
        taskId = self.getId()

        if not userId:
            userId = self.AuthenticatedMember()
        
        track = self.track.getLastUserTrack(taskId,0,userId)
        if track:
            return track.data
        return {}


    def saveUserResponse(self, data ,userId=None):
        taskId = self.getId()

        if not userId:
            userId = self.AuthenticatedMember()

        track = self.track.getLastUserTrack(taskId, 0, userId)
        if track is not None:
            return self.track.updateTrack(track, data)
        #self.track.saveUserTrack(taskId, 0, userId, data, update=True)


    def getDataUserId(self, runId, userId=None):
        if not userId:
            userId = self.AuthenticatedMember()

        track = self.track.getLastUserTrack(self.getId(), runId, userId)
        #print " getDataUserId ",track.data
        return track.data

    def getDataEvaluations(self, userId=None):
        if not userId:
            userId = self.AuthenticatedMember()
        tracks = self.track.getUserTracks(self.getId(), 0, userId)
        if tracks:
            ### Note: se tuvo que poner estas lineas para correguir el error de que un examen no tenga datos completos
            ###       esto sucede cuando un usuario inicia un examen dos veces el la mima hora y no ha terminado uno
            # for track in tracks:
            #     if len(track.data) < len(self.generateEvaluation()):
            #         return []
            return sorted(tracks, key=lambda x: x.timeStamp)
        return []

    def calculateScore(self, data):
        keys = len(data) # 
        interactions = (keys-5)/8  # 5 is evaluation data and 8 is the key of one interaction
        total_weigthing = self.getTotalWerighting(data)
        correct_answers = 0
        for e in range(interactions):
            result = data['question.%s.result'%e]
            if result:
                weighting = data['question.%s.weighting'%e]
                correct_answers += weighting        

        score_ratio = round((float(correct_answers)*100.0)/total_weigthing,2)
        return score_ratio

    def getTotalWerighting(self, data):
        return sum([self._getOb(data[i]).getWeighting() for i in data.keys() if i.endswith('.id')])            

    def formatTimeLeft(self, userId=None):
        if self.getMaxTimeResponseTest() <= 0 : return ''
        if not userId:
            userId = self.AuthenticatedMember()

        last = self.track.getLastUserTrack(self.getId(), 0, userId)
        testInfo = last.data.get('evaluation.start',time.time())

        secondsPassed = time.time() - testInfo
        secondsLeft = self.getMaxTimeResponseTest() * 60 - secondsPassed

        seconds = secondsLeft % 60
        minutes = (secondsLeft / 60) % 60
        hours   = secondsLeft / 3600

        return "%02d:%02d:%02d" % (hours, minutes, seconds)

    def getAllDataUsers(self):
        result = []
        tracks = sorted(self.track.values(),key=lambda x: x.userName)
        for track in tracks:
            tmp = {}
            tmp['userid'] = track.userName
            tmp['score'] = track.data['evaluation.score']
            tmp['start'] = self.utilConvertTime(track.data['evaluation.start'])
            tmp['end'] = self.utilConvertTime(track.data['evaluation.end'])
            tmp['time'] = self.utilCalculeTime(track.data['evaluation.start'], track.data['evaluation.end'])
            tmp['member'] = self.AuthenticatedMemberById(tmp['userid'])
            result.append(tmp)
        return result

    def getGroupDataUsers(self):

        results = []

        for k, g in groupby(self.track.values(), key=lambda r: r.userName):
            tmp = {}
            tmp['userid'] = k

            average = 0
            count = 0.0
                
            for data in g:
                #Let's fake data. This is quite, quite wrong!!!
                if not data.data.get('evaluation.scored_extra'):
                    #scored_extra = False
                    score_extra = 0.0
                else:
                    #scored_extra = data.data['evaluation.scored_extra']
                    score_extra = data.data['evaluation.score_extra']
                #Ends bad,dirty,evil hack

                if data.data['evaluation.scored']:
                    average += data.data['evaluation.score']
                    count += 1.0

            if not count:
                count = 1.0

            tmp['average'] = round(average/count,2)
            tmp['oportunities'] = range(1,len(g)+1)
            tmp['member'] = self.AuthenticatedMemberById(k)
            last_track = sorted(g, key=lambda x: x.timeStamp)[-1]
            tmp['score'] = last_track.data['evaluation.score']
            tmp['score_extra'] = score_extra
            tmp['scored_extra'] = score_extra
            tmp['start'] = self.utilConvertTime(last_track.data['evaluation.start'])
            tmp['end'] = self.utilConvertTime(last_track.data['evaluation.end'])
            tmp['time'] = self.utilCalculeTime(last_track.data['evaluation.start'], last_track.data['evaluation.end'])


            results.append(tmp)

        return results

    def getPendingGradeUsers(self):
        result = []
        tracks = sorted(self.track.values(),key=lambda x: x.userName)
        for track in tracks:
            if not track.data['evaluation.scored'] and track.data['evaluation.open']:            
                tmp = {}
                tmp['userid'] = track.userName
                tmp['score'] = track.data['evaluation.score']
                tmp['start'] = self.utilConvertTime(track.data['evaluation.start'])
                tmp['end'] = self.utilConvertTime(track.data['evaluation.end'])
                tmp['time'] = self.utilCalculeTime(track.data['evaluation.start'], track.data['evaluation.end'])
                tmp['member'] = self.AuthenticatedMemberById(tmp['userid'])
                result.append(tmp)
        return result

    def getOpenEvaluation(self, userId=None):
        results = []
        evaluation = self.getLastEvaluation(userId)
        total_questions = (len(evaluation)-5)/8
        for e in range(total_questions):
            if evaluation['question.%s.type'%e] == 'fill-in':
                question = {}
                question['index'] = e
                question['student_response'] = evaluation['question.%s.student_response'%e]
                question['time'] = self.utilConvertTimeHM(evaluation['question.%s.time'%e])
                question['weighting'] = evaluation['question.%s.weighting'%e]
                question['latency'] = evaluation['question.%s.latency'%e]
                qid = evaluation['question.%s.id'%e]
                question['title'] = self._getOb(qid).Title()

                result = evaluation['question.%s.result'%e]

                if result == '':
                    results.append(question)

        return results

    def setOpenEvaluation(self, data, userId=None):
        self.getLastEvaluation(userId)
        self.saveUserResponse(data)

    def getPendingUsers(self):
        users_evaluation = [x['userid'] for x in self.getGroupDataUsers()]
        parent = self.aq_inner.aq_parent     ## verificar que es un ExamContent
        parent = parent.aq_inner.aq_parent   ## verificar que es un Course
        users_course = parent.getRegisteredStudents()
        a = frozenset(users_course)
        b = frozenset(users_evaluation)
        c = a - b
        result = []
        for user in list(c):
            member = self.AuthenticatedMemberById(user)
            result.append(member)
        return result

    def utilCalculeTime(self, time1, time2):
        if not time1 or not time2:
            return "0"

        time1 = int(time1)
        time2 = int(time2)
        totalseconds = time2 - time1
        seconds = totalseconds % 60
        minutes = (totalseconds / 60) % 60
        hours   = totalseconds / 3600
        return "%02d:%02d:%02d" % (hours, minutes, seconds)

    def utilConvertTime(self, time1):
        if not time1:
            return "0"
        time1 = time.localtime(float(time1))
        time1 = time.strftime('%d/%m/%Y - %H:%M', time1)
        return time1

    def utilConvertTimeHM(self, time1):
        if time1 == '':
            time1 = 0.0
        n = float(time1)/1000.0
        time1 = time.localtime(n)
        time1 = time.strftime('%H:%M:%S', time1)
        return time1


    def haveOpportunity(self):
        # si sus evaluacions es menor al permitido
        # si el examen no esta vencido
        if len(self.getDataEvaluations()) < self.getMaxOpportunityTest():
            return True
        return False

    def haveTime(self):
        now            = DateTime()
        startPublished = self.getInitDate()
        endPublished   = self.getFinishDate()
        if((startPublished != None) and (startPublished > now)):
            return False
        if((endPublished != None) and (now > endPublished)):
            return False
        return True

    def AuthenticatedMember(self):
        portal_membership = getToolByName(self, 'portal_membership')
        return portal_membership.getAuthenticatedMember().getId()

    def AuthenticatedMemberById(self, userId=None):
        portal_membership = getToolByName(self, 'portal_membership')

        if not userId:
            return portal_membership.getAuthenticatedMember()

        member = portal_membership.getMemberById(userId)

        # Hack temporal que corrige error cuando un usuario es eliminado
        if not member:
            class Member:
                def getFullname(self):
                    return '--'
                def getPositionName(self):
                    return '--'
                def getPlace(self):
                    return '--'
                def getProductName(self):
                    return ['--',]
                def getDivisionName(self):
                    return ['--',]
                def getDistrict(self):
                    return '--'
                def getRegion(self):
                    return '--'
                def getEmployee(self):
                    return '--'
                def getIngress(self):
                    return '--'
                def getCountryName(self):
                    return '--'
                def getState(self):
                    return '--'
                def getCity(self):
                    return '--'
                def getPlace(self):
                    return '--'

            member = Member()

        return member

    #def sendMessage(self, data=None, isOpen=False, userId=None):
        #manager = getUtility(IMessagesManager)
        #receiver = sender = self.AuthenticatedMember()
        #if userId:
            #receiver = userId
        #subject = _(u"Exam name: ") + self.Title()
        #normal = _(u"Your grade is: ") + str(data['evaluation.score'])
        #openbody = _(u"The instructor has reviewed your exam and your grade was ")  + str(data['evaluation.score'])
        #link = _(u"To see the details of this revision, please follow this link: ") + "<a href=%s target=_blank> %s </a>"%(self.absolute_url(),self.Title())
        #body = normal + link
        #if isOpen:
            #body = openbody + link

        #manager.message_new(2, sender, receiver, subject, body)

    def setScoreKardex(self, data, userId=None):
        ## validate if the user is a eduMember or has kardex attribute
        member = self.AuthenticatedMemberById(userId)
        end = self.utilConvertTime(data['evaluation.end'])
        member.setDynamicKardex(evaluation=self.getEvaluationNameAndLink(),
                                date=end,
                                course=self.getCourseName(),
                                score=str(data['evaluation.score']),
                                type='exam')

    def getCourseName(self):
        parent = self.aq_inner.aq_parent     ## verificar que es un ExamContent
        parent = parent.aq_inner.aq_parent   ## verificar que es un Course
        return parent.Title()

    def getEvaluationNameAndLink(self):
        return self.Title() + '|' + self.absolute_url()

    #############
    # Permissions
    #############
    def hasModifyPortalContentPermission(self):
        """
        Return True if the current user can modify content on this context.
        """
        sm = getSecurityManager()
        return sm.checkPermission(ModifyPortalContent,self)
    #############
    # Statistics
    #############
    def getExamStatistics(self, attemp=0):
        sample = []
        for userid, g in groupby(self.track.values(), key=lambda r: r.userName):
            ## !!!! check if have more than one for filter the attemp
            data = sorted(g, key=lambda x: x.timeStamp)[attemp] # filter the attemp

            if data.data['evaluation.scored']:
                sample.append(data.data['evaluation.score'])

        if not sample:
            sample = [0]
            
        return Statistics(sample)


    def getQuestionsStatistics(self, attemp=0):
        """
        obtener todas las preguntas del examen
        crear diccionario con qid, question, correct, incorrect
        agregarlo a una lista

        """
        catalog = getToolByName(self, 'portal_catalog')
        return [ dict(qid=question.getId,
                      title=question.Title,
                      counts=self.getCountUserQuestions(question.getId, attemp=attemp))
                 for question in 
                 catalog(path=dict(query='/'.join(self.getPhysicalPath()),
                                   depth=1),)
                 ]


    def getCountUserQuestions(self, qid, attemp=0):
        correct = []
        incorrect = []

        for userid, g in groupby(self.track.values(), key=lambda r: r.userName):
            ## !!!! check if have more than one for filter the attemp
            data = sorted(g, key=lambda x: x.timeStamp)[attemp] # filter the attemp

            keys = len(data.data) # 
            interactions = (keys-5)/8  # 5 is evaluation data and 8 is the key of one interaction
            for e in range(interactions):
                if data.data['question.%s.id'%e] == qid:
                    result = data.data['question.%s.result'%e]
                    if result:
                        correct.append(userid)
                    else:
                        incorrect.append(userid)
                    break

        return (correct,incorrect)

    
    def getQuestionTitle(self, qid):
        return self._getOb(qid).Title()

    ########
    #  Module dependency
    ########
    #BUG
    def passEvaluation(self, userId=None):
        data = self.getLastEvaluation(userId)
        if not data:
            return False

        if data['evaluation.open'] and not data['evaluation.scored']:
            return True

        if data['evaluation.score'] < self.getMinScoreGrade():
            return False

        return True
    #BUG
    def passEvaluation(self, userId=None):
        taskId = self.getId()

        if not userId:
            userId = self.AuthenticatedMember()

        track = self.track.getLastUserTrack(taskId,0,userId)
        if track:
            score = track.data['evaluation.score']
            return score > self.getMinScoreGrade()
        return False


    def canTakeExam(self, userId=None):
        """
        """
        # comprobar si tiene asociado un examen

        if not userId:
            userId = self.AuthenticatedMember()

        evaluation = self.getEvaluationDependecy()
        if evaluation:
            return evaluation.passEvaluation(userId)
        return True

    def getPublishState(self):
        """
        """
        wftool = getToolByName(self, "portal_workflow")
        return wftool.getInfoFor(self, 'review_state')
class SCO(Item):
    implements(ISCO, ITTWLockable, INameFromTitle)
    portal_type = "SCO"

    title = u""
    description = u""
    filename = u""

    #track = None

    def __init__(self, id=None):
        super(SCO, self).__init__(id)
        # annotations = IAnnotations(self)
        # try:
        #     TRACK_KEY = self.UID()
        #     print 'TRACK_KEY',TRACK_KEY
        # except:
        #     TRACK_KEY = 'eduintelligent.sco.track'
        # self.track = annotations.setdefault(TRACK_KEY, TrackingStorage())
        self.track = TrackingStorage()

    # @property
    # def track(self):
    #     annotations = IAnnotations(self)
    #     TRACK_KEY = self.UID()
    #     print 'TRACK_KEY',TRACK_KEY
    #     return annotations.setdefault(TRACK_KEY, TrackingStorage())

    def getUrlContents(self):
        """
        """
        scoId = "/".join(self.getPhysicalPath())
        return EXTERNAL_URL + scoId

    def storePathSCO(self):
        """
        """
        scoId = "/".join(self.getPhysicalPath())
        path = os.path.join(CONTENT_STORE, scoId.lstrip('/'))
        return path

    def protectDirs(self):
        """
        :> robots.txt
        User-agent: *    # aplicable a todos
        Disallow: /      # impide la indexacion de todas las paginas
        ##################################
        :> .htaccess
        chmod 644 .htaccess
        IndexIgnore *
        """
        def walker(directory):
            for name in os.listdir(directory):
                path = os.path.join(directory, name)
                if os.path.isdir(path):
                    f = open(os.path.join(path, 'robots.txt'), 'w')
                    f.write("""User-agent: *\nDisallow: /
                    """)
                    f.close()
                    f = open(os.path.join(path, '.htaccess'), 'w')
                    f.write("""IndexIgnore *\n""")
                    f.close()
                    walker(path)
                    os.chmod(os.path.join(path, 'robots.txt'), 0644)
                    os.chmod(os.path.join(path, '.htaccess'), 0644)

        scoId = "/".join(self.getPhysicalPath())
        path = os.path.join(CONTENT_STORE, scoId.lstrip('/'))
        walker(path)

    def uploadContentPackage(self):
        """
        this is an event after create or edit 
        """
        specificPath = self.storePathSCO()
        print "Ruta en donde se almacena: ", specificPath
        if hasattr(self.filename, 'data'):
            if os.path.exists(specificPath):
                # we want to replace an existing directory:
                utilities.removeDirectory(specificPath)

            utilities.createDirectory(specificPath)

            utilities.unzip().extract(StringIO(str(self.filename.data)),
                                      specificPath)
            self.protectDirs()  ### create files to protect the public files

        self._v_manifest = None  # invalidate manifest after upload
        self._v_itemCount = None
        self.filename = None

    def getFileFromContentPackage(self, subpath, doStream=False):
        path = os.path.join(self.storePathSCO(), subpath)
        if not os.path.exists(path):
            print "cuidado, no existe el archivo!!"
            return ''
        f = open(path)
        return f.read()

    # IMS and SCORM stuff:

    _v_manifest = None
    _v_itemCount = None

    def getManifest(self):
        if self._v_manifest is None:
            xml = self.getFileFromContentPackage('imsmanifest.xml')
            if xml:
                self._v_manifest = IMSManifest(xml)
        return self._v_manifest

    def getReportData(self):
        result = []
        manifest = self.getManifest()
        # student is the student belonging to studentId or
        #the currently loggend-in user:
        #student = wbt.getStudent(self.studentId)
        #studentName = student and student.Title() or None
        if manifest:
            for org in self.getOrganizations():
                orgTitle = self.getObjTitle(org)
                showOrgTitle = True
                for item in manifest.getSubItems(org):
                    itemId = manifest.getIdentifier(item)
                    itemIder = manifest.getItemIdentifier(itemId)
                    row = {
                        'orgTitle': showOrgTitle and orgTitle or '',
                        'itemId': itemId,
                        'itemTitle': manifest.getTitle(item),
                        'itemLevel': manifest.getLevel(org, item),
                        'isStartable': itemIder,
                        'startResource': manifest.getStartResource(itemIder),
                        #'student': student,
                    }
                    showOrgTitle = False  # only show on first line
                    result.append(row)
        return result

    ##############################
    #    IMS Manifest Methods
    ##############################
    def getOrganizations(self):
        """
        at same time update the variable volatile self._v_manifest
        """
        manifest = self.getManifest()
        return manifest.getOrganizations()

    def getObjTitle(self, obj):
        return self._v_manifest and self._v_manifest.getTitle(obj) or ''

    def getItemCount(self):
        return self._v_manifest and self._v_manifest.getItemCount() or 0

    def getItemTitle(self, item=0):
        return self._v_manifest and self._v_manifest.getItemTitle(item) or ''

    def getMasteryScore(self, item=0):
        return self._v_manifest and self._v_manifest.getMasteryScore(
            item) or ''

    def getLaunchData(self, item=0):
        return self._v_manifest and self._v_manifest.getLaunchData(item) or ''

    def getItemIndex(self, itemId):
        return self._v_manifest and self._v_manifest.getItemIndex(itemId)

    def getItems(self, documentNode):
        return self._v_manifest and self._v_manifest.getItems(documentNode)

    def getTitle(self, nodeElem):
        return self._v_manifest and self._v_manifest.getTitle(nodeElem)

    def getIdentifier(self, nodeElem):
        return self._v_manifest and self._v_manifest.getIdentifier(nodeElem)

    def getIdentifierRef(self, nodeElem):
        return self._v_manifest and self._v_manifest.getIdentifierRef(nodeElem)

    #######################
    # SCORM Stuff
    #######################
    scormElements = {
        'cmi.core._children':
        'student_id,student_name,lesson_location,credit,'
        'lesson_status,entry,score,total_time,lesson_mode,exit,'
        'session_time',
        'cmi.core.score._children':
        'raw,min,max',
        #replace with current data:
        'cmi.core.student_name':
        'Unknown',
        'cmi.core.student_id':
        'unknown',
        'cmi.core.credit':
        'credit',  # should depend on lesson_mode
        'cmi.core.lesson_mode':
        'normal',
        #take from manifest:
        #'cmi.student_data.mastery_score': '',
        'cmi.launch_data':
        '',
        #replace with actuall data from lmca:
        'cmi.core.lesson_location':
        '',
        'cmi.core.lesson_status':
        'not attempted',
        'cmi.core.entry':
        'ab-initio',
        'cmi.core.score.raw':
        '0',
        'cmi.core.score.min':
        '',
        'cmi.core.score.max':
        '',
        'cmi.core.total_time':
        '0000:00:00.00',
        'cmi.core.session_time':
        '0000:00:00.00',
        'cmi.core.exit':
        '',
        'cmi.suspend_data':
        '',
        'cmi.comments':
        '',
        'cmi.comments_from_lms':
        '',
    }

    def getScormData(self, memberId=None, item=None):
        studentName, studentId = self.getAuthenticatedNameAndId(memberId)
        manifest = self.getManifest()
        elements = self.scormElements.copy()
        elements['cmi.core.student_id'] = studentId
        elements['cmi.core.student_name'] = studentName
        elements['cmi.launch_data'] = manifest.getLaunchData(item)
        # content must not set mastery score
        mastery = manifest.getMasteryScore(item)
        if (mastery != 'not found' and mastery != 'Item not found'):
            elements['cmi.student_data.mastery_score'] = mastery
        try:
            trackData = self.getLastUserTrack(item, 0, studentId)
            print "trackData", trackData
            elements.update(trackData['data'])
        except:
            print "\n\n\n################### el usuario", studentId, "no tiene registros"

        return elements

    def saveToUserTrack(self, data, memberId=None, item=None):
        """ Store data (a mapping) in the user's scorm track. """
        studentName, studentId = self.getAuthenticatedNameAndId(memberId)
        if studentId is None:
            return {}
        # setting lesson status
        self.setLessonStatus(data, self.getScormData(memberId, item))

        data['cmi.core.entry'] = 'resume'

        data['cmi.core.total_time'] = addScormTime(
            data['cmi.core.total_time'], data['cmi.core.session_time'])

        self.recordTrack(item, 0, studentId, data)

    def setLessonStatus(self, data, scormData):
        status = data.get('cmi.core.lesson_status', None)
        entry = scormData.get('cmi.core.entry', 'resume')
        if status is None \
                and scormData.get('cmi.core.lesson_mode', 'normal') == 'browse':
            status = 'browsed'
        if status is None:
            score = self._scormData.get('cmi.core.score.raw', '')
            masteryScore = scormData['cmi.student_data.mastery_score']
            if masteryScore != '' and score != '' and int(score) >= int(
                    masteryScore):
                status = 'passed'
        if status is None:
            if scormData.get('self.cmi.core.exit', '') == 'suspend':
                status = 'incomplete'
            # not yet supported by 21LL AK:
            #elif masteryScore != '' and score != '' and int(score) < int(masteryScore):
            #    status = 'failed'
            else:
                status = 'browsed'
        data['cmi.core.lesson_status'] = status
        if status in ('complete', 'passed', 'failed'):
            entry = ''
        else:
            entry = 'resume'
        # maybe the best solution:
        #entry = ''
        #entry = 'ab-initio'
        data['cmi.core.entry'] = entry
        return data

    def startRun(self, assessmentId):
        """ """
        return self.track.startRun(assessmentId)

    def stopRun(self, assessmentId):
        """ """
        return self.track.stopRun(assessmentId)

    # the"classical" way of storing a track

    def recordTrack(self, assessmentId, runId, userName, data):
        """ """
        print "recordTrack", self.track
        print "recordTrack(data)", data
        self.track.saveUserTrack(assessmentId, int(runId), userName, data)
        return runId or self.track.currentRuns[assessmentId]

    # SCORM-conformant access. Note that the data given (element names
    # and values) must conform to the SCORM data model.

    def scormSetValue(self, assessmentId, runId, userName, element, value):
        """ """
        self.scormAPI.init(assessmentId, int(runId), userName)
        return self.scormAPI.setValue(element, value)

    def scormSetValues(self, assessmentId, runId, userName, mapping):
        """ """
        self.scormAPI.init(assessmentId, int(runId), userName)
        return self.scormAPI.setValues(mapping)

    def scormGetValue(self, assessmentId, runId, userName, element):
        """ """
        self.scormAPI.init(assessmentId, int(runId), userName)
        return self.scormAPI.getValue(element)

    # query methods

    def getTaskIds(self):
        """ """
        return list(self.track.getTaskIds())

    def getUserNames(self, taskId):
        """ """
        return self.track.getUserNames(taskId)

    def listTracks(self, assessmentId, userName):
        """ """
        criteria = {}
        if assessmentId:
            criteria['taskId'] = assessmentId
        if userName:
            criteria['userName'] = userName
        tracks = self.track.query(**criteria)
        return [self.trackToDict(t) for t in tracks]

    def getLastUserTrack(self, assessmentIdId, runId, userName):
        """ """
        track = self.track.getLastUserTrack(assessmentIdId, int(runId),
                                            userName)
        return self.trackToDict(track)

    def query(self, criteria):
        """ """
        if 'assessmentId' in criteria:
            criteria['taskId'] = criteria['assessmentId']
            del criteria['assessmentId']
        tracks = self.track.query(**criteria)
        return [self.trackToDict(t) for t in tracks]

    def trackToDict(self, track):
        result = track.metadata
        result['timeStamp'] = timeStamp2ISO(result['timeStamp'])
        result['assessmentId'] = result['taskId']
        result['data'] = track.data and dict(track.data) or {}
        return result

        ######################################
        # User Authentication
        ######################################

    def getAuthenticatedNameAndId(self, memberId=None):
        """
        """
        memberName = "??????"
        portal_membership = getToolByName(self, 'portal_membership')
        if memberId:
            member = portal_membership.getMemberById(memberId)
        else:
            member = portal_membership.getAuthenticatedMember()

        if member is not None:
            memberId = member.getId()
            memberName = member.getProperty('fullname') or memberName
            memberName = memberName.split(' ')[0]

        return memberName, memberId
class SCO(Item):
    implements(ISCO, ITTWLockable, INameFromTitle)
    portal_type = "SCO"

    title = u""
    description = u""
    filename = u""
    # track = None

    def __init__(self, id=None):
        super(SCO, self).__init__(id)
        # annotations = IAnnotations(self)
        # try:
        #     TRACK_KEY = self.UID()
        #     print 'TRACK_KEY',TRACK_KEY
        # except:
        #     TRACK_KEY = 'eduintelligent.sco.track'
        # self.track = annotations.setdefault(TRACK_KEY, TrackingStorage())
        self.track = TrackingStorage()

    # @property
    # def track(self):
    #     annotations = IAnnotations(self)
    #     TRACK_KEY = self.UID()
    #     print 'TRACK_KEY',TRACK_KEY
    #     return annotations.setdefault(TRACK_KEY, TrackingStorage())

    def getUrlContents(self):
        """
        """
        scoId = "/".join(self.getPhysicalPath())
        return EXTERNA_URL + scoId

    def storePathSCO(self):
        """
        """
        scoId = "/".join(self.getPhysicalPath())
        path = os.path.join(SCORM_STORE, scoId.lstrip("/"))
        return path

    def protectDirs(self):
        """
        :> robots.txt
        User-agent: *    # aplicable a todos
        Disallow: /      # impide la indexacion de todas las paginas
        ##################################
        :> .htaccess
        chmod 644 .htaccess
        IndexIgnore *
        """

        def walker(directory):
            for name in os.listdir(directory):
                path = os.path.join(directory, name)
                if os.path.isdir(path):
                    f = open(os.path.join(path, "robots.txt"), "w")
                    f.write(
                        """User-agent: *\nDisallow: /
                    """
                    )
                    f.close()
                    f = open(os.path.join(path, ".htaccess"), "w")
                    f.write("""IndexIgnore *\n""")
                    f.close()
                    walker(path)
                    os.chmod(os.path.join(path, "robots.txt"), 0644)
                    os.chmod(os.path.join(path, ".htaccess"), 0644)

        scoId = "/".join(self.getPhysicalPath())
        path = os.path.join(SCORM_STORE, scoId.lstrip("/"))
        walker(path)

    def uploadContentPackage(self):
        """
        this is an event after create or edit 
        """
        specificPath = self.storePathSCO()
        print "Ruta en donde se almacena: ", specificPath
        if hasattr(self.filename, "data"):
            if os.path.exists(specificPath):
                # we want to replace an existing directory:
                utilities.removeDirectory(specificPath)

            utilities.createDirectory(specificPath)

            utilities.unzip().extract(StringIO(str(self.filename.data)), specificPath)
            self.protectDirs()  ### create files to protect the public files

        self._v_manifest = None  # invalidate manifest after upload
        self._v_itemCount = None
        self.filename = None

    def getFileFromContentPackage(self, subpath, doStream=False):
        path = os.path.join(self.storePathSCO(), subpath)
        if not os.path.exists(path):
            print "cuidado, no existe el archivo!!"
            return ""
        f = open(path)
        return f.read()

    # IMS and SCORM stuff:

    _v_manifest = None
    _v_itemCount = None

    def getManifest(self):
        if self._v_manifest is None:
            xml = self.getFileFromContentPackage("imsmanifest.xml")
            if xml:
                self._v_manifest = IMSManifest(xml)
        return self._v_manifest

    def getReportData(self):
        result = []
        manifest = self.getManifest()
        # student is the student belonging to studentId or the currently loggend-in user:
        # student = wbt.getStudent(self.studentId)
        # studentName = student and student.Title() or None
        if manifest:
            for org in self.getOrganizations():
                orgTitle = self.getObjTitle(org)
                showOrgTitle = True
                for item in manifest.getSubItems(org):
                    itemId = manifest.getIdentifier(item)
                    itemIder = manifest.getItemIdentifier(itemId)
                    row = {
                        "orgTitle": showOrgTitle and orgTitle or "",
                        "itemId": itemId,
                        "itemTitle": manifest.getTitle(item),
                        "itemLevel": manifest.getLevel(org, item),
                        "isStartable": itemIder,
                        "startResource": manifest.getStartResource(itemIder),
                        #'student': student,
                    }
                    showOrgTitle = False  # only show on first line
                    result.append(row)
        return result

    ##############################
    #    IMS Manifest Methods
    ##############################
    def getOrganizations(self):
        """
        at same time update the variable volatile self._v_manifest
        """
        manifest = self.getManifest()
        return manifest.getOrganizations()

    def getObjTitle(self, obj):
        return self._v_manifest and self._v_manifest.getTitle(obj) or ""

    def getItemCount(self):
        return self._v_manifest and self._v_manifest.getItemCount() or 0

    def getItemTitle(self, item=0):
        return self._v_manifest and self._v_manifest.getItemTitle(item) or ""

    def getMasteryScore(self, item=0):
        return self._v_manifest and self._v_manifest.getMasteryScore(item) or ""

    def getLaunchData(self, item=0):
        return self._v_manifest and self._v_manifest.getLaunchData(item) or ""

    def getItemIndex(self, itemId):
        return self._v_manifest and self._v_manifest.getItemIndex(itemId)

    def getItems(self, documentNode):
        return self._v_manifest and self._v_manifest.getItems(documentNode)

    def getTitle(self, nodeElem):
        return self._v_manifest and self._v_manifest.getTitle(nodeElem)

    def getIdentifier(self, nodeElem):
        return self._v_manifest and self._v_manifest.getIdentifier(nodeElem)

    def getIdentifierRef(self, nodeElem):
        return self._v_manifest and self._v_manifest.getIdentifierRef(nodeElem)

    #######################
    # SCORM Stuff
    #######################
    scormElements = {
        "cmi.core._children": "student_id,student_name,lesson_location,credit,"
        "lesson_status,entry,score,total_time,lesson_mode,exit,session_time",
        "cmi.core.score._children": "raw,min,max",
        # replace with current data:
        "cmi.core.student_name": "Unknown",
        "cmi.core.student_id": "unknown",
        "cmi.core.credit": "credit",  # should depend on lesson_mode
        "cmi.core.lesson_mode": "normal",
        # take from manifest:
        #'cmi.student_data.mastery_score': '',
        "cmi.launch_data": "",
        # replace with actuall data from lmca:
        "cmi.core.lesson_location": "",
        "cmi.core.lesson_status": "not attempted",
        "cmi.core.entry": "ab-initio",
        "cmi.core.score.raw": "0",
        "cmi.core.score.min": "",
        "cmi.core.score.max": "",
        "cmi.core.total_time": "0000:00:00.00",
        "cmi.core.session_time": "0000:00:00.00",
        "cmi.core.exit": "",
        "cmi.suspend_data": "",
        "cmi.comments": "",
        "cmi.comments_from_lms": "",
    }

    def getScormData(self, memberId=None, item=None):
        studentName, studentId = self.getAuthenticatedNameAndId(memberId)
        manifest = self.getManifest()
        elements = self.scormElements.copy()
        elements["cmi.core.student_id"] = studentId
        elements["cmi.core.student_name"] = studentName
        elements["cmi.launch_data"] = manifest.getLaunchData(item)
        # content must not set mastery score
        mastery = manifest.getMasteryScore(item)
        if mastery != "not found" and mastery != "Item not found":
            elements["cmi.student_data.mastery_score"] = mastery
        try:
            trackData = self.getLastUserTrack(item, 0, studentId)
            print "trackData", trackData
            elements.update(trackData["data"])
        except:
            print "\n\n\n################### el usuario", studentId, "no tiene registros"

        return elements

    def saveToUserTrack(self, data, memberId=None, item=None):
        """ Store data (a mapping) in the user's scorm track. """
        studentName, studentId = self.getAuthenticatedNameAndId(memberId)
        if studentId is None:
            return {}
        # setting lesson status
        self.setLessonStatus(data, self.getScormData(memberId, item))

        data["cmi.core.entry"] = "resume"

        data["cmi.core.total_time"] = addScormTime(data["cmi.core.total_time"], data["cmi.core.session_time"])

        self.recordTrack(item, 0, studentId, data)

    def setLessonStatus(self, data, scormData):
        status = data.get("cmi.core.lesson_status", None)
        entry = scormData.get("cmi.core.entry", "resume")
        if status is None and scormData.get("cmi.core.lesson_mode", "normal") == "browse":
            status = "browsed"
        if status is None:
            score = self._scormData.get("cmi.core.score.raw", "")
            masteryScore = scormData["cmi.student_data.mastery_score"]
            if masteryScore != "" and score != "" and int(score) >= int(masteryScore):
                status = "passed"
        if status is None:
            if scormData.get("self.cmi.core.exit", "") == "suspend":
                status = "incomplete"
            # not yet supported by 21LL AK:
            # elif masteryScore != '' and score != '' and int(score) < int(masteryScore):
            #    status = 'failed'
            else:
                status = "browsed"
        data["cmi.core.lesson_status"] = status
        if status in ("complete", "passed", "failed"):
            entry = ""
        else:
            entry = "resume"
        # maybe the best solution:
        # entry = ''
        # entry = 'ab-initio'
        data["cmi.core.entry"] = entry
        return data

    def startRun(self, assessmentId):
        """ """
        return self.track.startRun(assessmentId)

    def stopRun(self, assessmentId):
        """ """
        return self.track.stopRun(assessmentId)

    # the"classical" way of storing a track

    def recordTrack(self, assessmentId, runId, userName, data):
        """ """
        print "recordTrack", self.track
        print "recordTrack(data)", data
        self.track.saveUserTrack(assessmentId, int(runId), userName, data)
        return runId or self.track.currentRuns[assessmentId]

    # SCORM-conformant access. Note that the data given (element names
    # and values) must conform to the SCORM data model.

    def scormSetValue(self, assessmentId, runId, userName, element, value):
        """ """
        self.scormAPI.init(assessmentId, int(runId), userName)
        return self.scormAPI.setValue(element, value)

    def scormSetValues(self, assessmentId, runId, userName, mapping):
        """ """
        self.scormAPI.init(assessmentId, int(runId), userName)
        return self.scormAPI.setValues(mapping)

    def scormGetValue(self, assessmentId, runId, userName, element):
        """ """
        self.scormAPI.init(assessmentId, int(runId), userName)
        return self.scormAPI.getValue(element)

    # query methods

    def getTaskIds(self):
        """ """
        return list(self.track.getTaskIds())

    def getUserNames(self, taskId):
        """ """
        return self.track.getUserNames(taskId)

    def listTracks(self, assessmentId, userName):
        """ """
        criteria = {}
        if assessmentId:
            criteria["taskId"] = assessmentId
        if userName:
            criteria["userName"] = userName
        tracks = self.track.query(**criteria)
        return [self.trackToDict(t) for t in tracks]

    def getLastUserTrack(self, assessmentIdId, runId, userName):
        """ """
        track = self.track.getLastUserTrack(assessmentIdId, int(runId), userName)
        return self.trackToDict(track)

    def query(self, criteria):
        """ """
        if "assessmentId" in criteria:
            criteria["taskId"] = criteria["assessmentId"]
            del criteria["assessmentId"]
        tracks = self.track.query(**criteria)
        return [self.trackToDict(t) for t in tracks]

    def trackToDict(self, track):
        result = track.metadata
        result["timeStamp"] = timeStamp2ISO(result["timeStamp"])
        result["assessmentId"] = result["taskId"]
        result["data"] = track.data and dict(track.data) or {}
        return result

        ######################################
        # User Authentication
        ######################################

    def getAuthenticatedNameAndId(self, memberId=None):
        """
        """
        memberName = "??????"
        portal_membership = getToolByName(self, "portal_membership")
        if memberId:
            member = portal_membership.getMemberById(memberId)
        else:
            member = portal_membership.getAuthenticatedMember()

        if member is not None:
            memberId = member.getId()
            memberName = member.getProperty("fullname") or memberName
            memberName = memberName.split(" ")[0]

        return memberName, memberId