diff --git a/.hgignore b/.hgignore new file mode 100644 index 00000000000..4c1284b47dd --- /dev/null +++ b/.hgignore @@ -0,0 +1,23 @@ +syntax: glob + +# Downloaded and locally built eggs +eggs +scripts/scramble/build +scripts/scramble/lib +scripts/scramble/archives + +# Database stuff +database/beaker_sessions +database/compiled_templates +database/files +database/*.sqlite + +# Python bytecode +*.pyc + +# Config files +universe_wsgi.ini +reports_wsgi.ini +datatypes_conf.xml +tool_conf.xml + diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py index 267b63f1f1e..fc325b33601 100644 --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -39,6 +39,7 @@ class Configuration( object ): self.tool_config = resolve_path( kwargs.get( 'tool_config_file', 'tool_conf.xml' ), self.root ) self.tool_secret = kwargs.get( "tool_secret", "" ) self.id_secret = kwargs.get( "id_secret", "USING THE DEFAULT IS NOT SECURE!" ) + self.set_metadata_externally = string_as_bool( kwargs.get( "set_metadata_externally", "False" ) ) self.use_remote_user = string_as_bool( kwargs.get( "use_remote_user", "False" ) ) self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None ) self.require_login = string_as_bool( kwargs.get( "require_login", "False" ) ) @@ -112,7 +113,17 @@ class Configuration( object ): for path in self.tool_config, self.datatypes_config: if not os.path.isfile(path): raise ConfigurationError("File not found: %s" % path ) - + + def is_admin_user( self,user ): + """ + Determine if the provided user is listed in `admin_users`. + + NOTE: This is temporary, admin users will likely be specified in the + database in the future. + """ + admin_users = self.get( "admin_users", "" ).split( "," ) + return ( user is not None and user.email in admin_users ) + def get_database_engine_options( kwargs ): """ Allow options for the SQLAlchemy database engine to be passed by using diff --git a/lib/galaxy/datatypes/interval.py b/lib/galaxy/datatypes/interval.py index 5e4fdcaa4cc..f0c4236e2e8 100644 --- a/lib/galaxy/datatypes/interval.py +++ b/lib/galaxy/datatypes/interval.py @@ -731,12 +731,16 @@ class Wiggle( Tabular ): if line and not line.startswith( '#' ): elems = line.split( '\t' ) try: - int( elems[0] ) + float( elems[0] ) #"Wiggle track data values can be integer or real, positive or negative values" break except: + do_break = False for str in data.col1_startswith: if elems[0].lower().startswith(str): + do_break = True break + if do_break: + break Tabular.set_meta( self, dataset, overwrite = overwrite, skip = i ) def sniff( self, filename ): diff --git a/lib/galaxy/datatypes/metadata.py b/lib/galaxy/datatypes/metadata.py index f0e6e9d6958..8865cbfcda7 100644 --- a/lib/galaxy/datatypes/metadata.py +++ b/lib/galaxy/datatypes/metadata.py @@ -1,6 +1,6 @@ -import sys, logging, copy, shutil, weakref +import sys, logging, copy, shutil, weakref, cPickle, tempfile, os -from galaxy.util import string_as_bool +from galaxy.util import string_as_bool, relpath from galaxy.util.odict import odict from galaxy.web import form_builder import galaxy.model @@ -9,6 +9,8 @@ log = logging.getLogger( __name__ ) STATEMENTS = "__galaxy_statements__" #this is the name of the property in a Datatype class where new metadata spec element Statements are stored +DATABASE_CONNECTION_AVAILABLE = True #When False, certain metadata parameter types (see FileParameter) will behave differently + class Statement( object ): """ This class inserts its target into a list in the surrounding @@ -28,7 +30,7 @@ class Statement( object ): statement.target( element, *args, **kwargs ) #statement.target is MetadataElementSpec, element is a Datatype class -class MetadataCollection: +class MetadataCollection( object ): """ MetadataCollection is not a collection at all, but rather a proxy to the real metadata which is stored as a Dictionary. This class @@ -90,6 +92,26 @@ class MetadataCollection: if key in self.spec: rval[key] = self.spec[key].param.make_copy( value, target_context=self, source_context=to_copy ) return rval + def from_pickled_dict( self, filename ): + dataset = self.parent + log.debug( 'loading metadata from file for: %s %s' % ( dataset.__class__.__name__, dataset.id ) ) + pickled_dict = cPickle.load( open( filename ) ) + for name, spec in self.spec.items(): + if name in pickled_dict: + dataset._metadata[ name ] = spec.param.from_pickle_value( pickled_dict[ name ], dataset ) + elif name in dataset._metadata: + #if the metadata value is not found in our externally set metadata but it has a value in the 'old' + #metadata associated with our dataset, we'll delete it from our dataset's metadata dict + del dataset._metadata[ name ] + def to_pickled_dict( self, filename ): + meta_dict = {} + dataset_meta_dict = self.parent._metadata + for name, spec in self.spec.items(): + if name in dataset_meta_dict: + meta_dict[ name ] = spec.param.to_pickle_value( dataset_meta_dict[ name ] ) + cPickle.dump( meta_dict, open( filename, 'wb+' ) ) + def __getstate__( self ): + return None #cannot pickle a weakref item (self._parent), when data._metadata_collection is None, it will be recreated on demand class MetadataSpecCollection( odict ): """ @@ -168,6 +190,17 @@ class MetadataParameter( object ): """ return value + def from_pickle_value( self, value, parent ): + """ + Turns a value read from a pickled dict into its value to be pushed directly into the metadata dict. + """ + return value + def to_pickle_value( self, value ): + """ + Turns a value read from a metadata into its value to be pushed directly into the pickled dict. + """ + return value + class MetadataElementSpec( object ): """ Defines a metadata element and adds it to the metadata_spec (which @@ -326,14 +359,19 @@ class FileParameter( MetadataParameter ): return "
No display available for Metadata Files
" def wrap( self, value ): - if isinstance( value, galaxy.model.MetadataFile ): + if isinstance( value, galaxy.model.MetadataFile ) or isinstance( value, MetadataTempFile ): return value - try: - return galaxy.model.MetadataFile.get( value ) - except: - #value was not a valid id - return None - + if DATABASE_CONNECTION_AVAILABLE: + try: + return galaxy.model.MetadataFile.get( value ) + except: + #value was not a valid id + return None + elif value is not None: + mf = galaxy.model.MetadataFile() + mf.id = value #we assume this is a valid id, since we cannot check it + return mf + return None def make_copy( self, value, target_context = None, source_context = None ): value = self.wrap( value ) if value: @@ -342,9 +380,146 @@ class FileParameter( MetadataParameter ): shutil.copy( value.file_name, new_value.file_name ) return self.unwrap( new_value ) return None - + @classmethod def marshal( cls, value ): if isinstance( value, galaxy.model.MetadataFile ): value = value.id return value + + def from_pickle_value( self, value, parent ): + """ + Turns a value read from a pickled dict into its value to be pushed directly into the metadata dict. + """ + if isinstance( value, MetadataTempFile ): + mf = self.new_file( dataset = parent, **value.kwd ) + shutil.move( value.file_name, mf.file_name ) + value = mf.id + return value + def to_pickle_value( self, value ): + """ + Turns a value read from a metadata into its value to be pushed directly into the pickled dict. + """ + if isinstance( value, galaxy.model.MetadataFile ): + value = value.id + return value + + def new_file( self, dataset = None, **kwd ): + if DATABASE_CONNECTION_AVAILABLE: + mf = galaxy.model.MetadataFile( name = self.spec.name, dataset = dataset, **kwd ) + mf.flush() #flush to assign id + return mf + else: + #we need to make a tmp file that is accessable to the head node, + #we will be copying its contents into the MetadataFile objects filename after restoring from pickle + #we do not include 'dataset' in the kwds passed, as from_pickle_value() will handle this for us + return MetadataTempFile( **kwd ) + +#This class is used when a database file connection is not available +class MetadataTempFile( object ): + tmp_dir = 'database/tmp' #this should be overwritten as necessary in calling scripts + def __init__( self, **kwd ): + self.kwd = kwd + self._filename = None + @property + def file_name( self ): + if self._filename is None: + #we need to create a tmp file, accessable across all nodes/heads, save the name, and return it + self._filename = relpath( tempfile.NamedTemporaryFile( dir = self.tmp_dir, prefix = "metadata_temp_file_" ).name ) + open( self._filename, 'wb+' ) #create an empty file, so it can't be reused using tempfile + return self._filename + @classmethod + def cleanup_from_pickled_dict_filename( cls, filename ): + try: + for key, value in cPickle.load( open( filename ) ).items(): + if isinstance( value, cls ) and os.path.exists( value.file_name ): + log.debug( 'Cleaning up abandoned MetadataTempFile file: %s' % value.file_name ) + os.unlink( value.file_name ) + except Exception, e: + log.debug( 'Failed to cleanup MetadataTempFile temp files from %s: %s' % ( filename, e ) ) + +#Class with methods allowing set_meta() to be called externally to the Galaxy head +class JobExternalOutputMetadataWrapper( object ): + #this class allows access to external metadata filenames for all outputs associated with a job + def __init__( self, job ): + self.job_id = job.id + def get_output_filenames_by_dataset( self, dataset ): + if isinstance( dataset, galaxy.model.HistoryDatasetAssociation ): + return galaxy.model.JobExternalOutputMetadata.filter_by( job_id = self.job_id, history_dataset_association_id = dataset.id ).first() #there should only be one or None + elif isinstance( dataset, galaxy.model.LibraryDatasetDatasetAssociation ): + return galaxy.model.JobExternalOutputMetadata.filter_by( job_id = self.job_id, library_dataset_dataset_association_id = dataset.id ).first() #there should only be one or None + return None + def get_dataset_metadata_key( self, dataset ): + return "%s_%d" % ( dataset.__class__.__name__, dataset.id ) #set meta can be called on library items and history items, need to make different keys for them, since ids can overlap + def setup_external_metadata( self, datasets, exec_dir = None, tmp_dir = None, dataset_files_path = None, kwds = {} ): + #fill in metadata_files_dict and return the command with args required to set metadata + def __metadata_files_list_to_cmd_line( metadata_files ): + return "%s,%s,%s,%s" % ( metadata_files.filename_in, metadata_files.filename_kwds, metadata_files.filename_out, metadata_files.filename_results_code ) + if not isinstance( datasets, list ): + datasets = [ datasets ] + if exec_dir is None: + exec_dir = os.path.abspath( os.getcwd() ) + if tmp_dir is None: + tmp_dir = MetadataTempFile.tmp_dir + if dataset_files_path is None: + dataset_files_path = galaxy.model.Dataset.file_path + metadata_files_list = [] + for dataset in datasets: + key = self.get_dataset_metadata_key( dataset ) + #future note: + #wonkiness in job execution causes build command line to be called more than once + #when setting metadata externally, via 'auto-detect' button in edit attributes, etc., + #we don't want to overwrite (losing the ability to cleanup) our existing dataset keys and files, + #so we will only populate the dictionary once + metadata_files = self.get_output_filenames_by_dataset( dataset ) + if not metadata_files: + metadata_files = galaxy.model.JobExternalOutputMetadata( dataset = dataset) + metadata_files.job_id = self.job_id + #we are using tempfile to create unique filenames, tempfile always returns an absolute path + #we will use pathnames relative to the galaxy root, to accommodate instances where the galaxy root + #is located differently, i.e. on a cluster node with a different filesystem structure + + #file to store existing dataset + metadata_files.filename_in = relpath( tempfile.NamedTemporaryFile( dir = tmp_dir, prefix = "metadata_in_%s_" % key ).name ) + cPickle.dump( dataset, open( metadata_files.filename_in, 'wb+' ) ) + #file to store metadata results of set_meta() + metadata_files.filename_out = relpath( tempfile.NamedTemporaryFile( dir = tmp_dir, prefix = "metadata_out_%s_" % key ).name ) + open( metadata_files.filename_out, 'wb+' ) # create the file on disk, so it cannot be reused by tempfile (unlikely, but possible) + #file to store a 'return code' indicating the results of the set_meta() call + #results code is like (True/False - if setting metadata was successful/failed , exception or string of reason of success/failure ) + metadata_files.filename_results_code = relpath( tempfile.NamedTemporaryFile( dir = tmp_dir, prefix = "metadata_out_%s_" % key ).name ) + cPickle.dump( ( False, 'External set_meta() not called' ), open( metadata_files.filename_results_code, 'wb+' ) ) # create the file on disk, so it cannot be reused by tempfile (unlikely, but possible) + #file to store kwds passed to set_meta() + metadata_files.filename_kwds = relpath( tempfile.NamedTemporaryFile( dir = tmp_dir, prefix = "metadata_kwds_%s_" % key ).name ) + cPickle.dump( kwds, open( metadata_files.filename_kwds, 'wb+' ) ) + metadata_files.flush() + metadata_files_list.append( metadata_files ) + #return command required to build + return "%s %s %s %s" % ( os.path.join( exec_dir, 'set_metadata.sh' ), dataset_files_path, tmp_dir, " ".join( map( __metadata_files_list_to_cmd_line, metadata_files_list ) ) ) + + def external_metadata_set_successfully( self, dataset ): + metadata_files = self.get_output_filenames_by_dataset( dataset ) + if not metadata_files: + return False # this file doesn't exist + rval, rstring = cPickle.load( open( metadata_files.filename_results_code ) ) + if not rval: + log.debug( 'setting metadata externally failed for %s %s: %s' % ( dataset.__class__.__name__, dataset.id, rstring ) ) + return rval + + def cleanup_external_metadata( self ): + log.debug( 'Cleaning up external metadata files' ) + for metadata_files in galaxy.model.Job.get( self.job_id ).external_output_metadata: + #we need to confirm that any MetadataTempFile files were removed, if not we need to remove them + #can occur if the job was stopped before completion, but a MetadataTempFile is used in the set_meta + MetadataTempFile.cleanup_from_pickled_dict_filename( metadata_files.filename_out ) + dataset_key = self.get_dataset_metadata_key( metadata_files.dataset ) + for key, fname in [ ( 'filename_in', metadata_files.filename_in ), ( 'filename_out', metadata_files.filename_out ), ( 'filename_results_code', metadata_files.filename_results_code ), ( 'filename_kwds', metadata_files.filename_kwds ) ]: + try: + os.remove( fname ) + except Exception, e: + log.debug( 'Failed to cleanup external metadata file (%s) for %s: %s' % ( key, dataset_key, e ) ) + def set_job_runner_external_pid( self, pid ): + for metadata_files in galaxy.model.Job.get( self.job_id ).external_output_metadata: + metadata_files.job_runner_external_pid = pid + metadata_files.flush() + diff --git a/lib/galaxy/datatypes/sequence.py b/lib/galaxy/datatypes/sequence.py index 10d54cffa03..d8d9f076af9 100644 --- a/lib/galaxy/datatypes/sequence.py +++ b/lib/galaxy/datatypes/sequence.py @@ -254,8 +254,7 @@ class Maf( Alignment ): tmp_file.write( "%s\t%s\n" % ( spec, "\t".join( chroms ) ) ) if not chrom_file: - chrom_file = galaxy.model.MetadataFile( dataset = dataset, name = "species_chromosomes" ) - chrom_file.flush() + chrom_file = dataset.metadata.spec['species_chromosomes'].param.new_file( dataset = dataset ) tmp_file.seek( 0 ) open( chrom_file.file_name, 'wb' ).write( tmp_file.read() ) dataset.metadata.species_chromosomes = chrom_file @@ -263,8 +262,7 @@ class Maf( Alignment ): index_file = dataset.metadata.maf_index if not index_file: - index_file = galaxy.model.MetadataFile( dataset = dataset, name="maf_index" ) - index_file.flush() + index_file = dataset.metadata.spec['maf_index'].param.new_file( dataset = dataset ) indexes.write( open( index_file.file_name, 'w' ) ) dataset.metadata.maf_index = index_file diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py index 792da30a250..91962e3c9ff 100644 --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -4,6 +4,7 @@ from galaxy import util, model from galaxy.model import mapping from galaxy.datatypes.tabular import * from galaxy.datatypes.interval import * +from galaxy.datatypes import metadata import pkg_resources pkg_resources.require( "PasteDeploy" ) @@ -306,6 +307,7 @@ class JobWrapper( object ): self.working_directory = \ os.path.join( self.app.config.job_working_directory, str( self.job_id ) ) self.output_paths = None + self.external_output_metadata = metadata.JobExternalOutputMetadataWrapper( job ) #wrapper holding the info required to restore and clean up from pickled files used for setting metadata externally def get_param_dict( self ): """ @@ -462,6 +464,7 @@ class JobWrapper( object ): self.fail( "Job %s's output dataset(s) could not be read" % job.id ) return for dataset_assoc in job.output_datasets: + #should this also be checking library associations? - can a library item be added from a history before the job has ended? - lets not allow this to occur for dataset in dataset_assoc.dataset.dataset.history_associations: #need to update all associated output hdas, i.e. history was shared with job running dataset.blurb = 'done' dataset.peek = 'no peek' @@ -470,13 +473,25 @@ class JobWrapper( object ): if stderr: dataset.blurb = "error" elif dataset.has_data(): - # Only set metadata values if they are missing... - dataset.set_meta( overwrite = False ) + #if a dataset was copied, it won't appear in our dictionary: + #either use the metadata from originating output dataset, or call set_meta on the copies + #it would be quicker to just copy the metadata from the originating output dataset, + #but somewhat trickier (need to recurse up the copied_from tree), for now we'll call set_meta() + if not self.external_output_metadata.external_metadata_set_successfully( dataset ): + # Only set metadata values if they are missing... + dataset.set_meta( overwrite = False ) + else: + #load metadata from file + #we need to no longer allow metadata to be edited while the job is still running, + #since if it is edited, the metadata changed on the running output will no longer match + #the metadata that was stored to disk for use via the external process, + #and the changes made by the user will be lost, without warning or notice + dataset.metadata.from_pickled_dict( self.external_output_metadata.get_output_filenames_by_dataset( dataset ).filename_out ) dataset.set_peek() else: dataset.blurb = "empty" dataset.flush() - if stderr: + if stderr: dataset_assoc.dataset.dataset.state = model.Dataset.states.ERROR else: dataset_assoc.dataset.dataset.state = model.Dataset.states.OK @@ -517,10 +532,12 @@ class JobWrapper( object ): def cleanup( self ): # remove temporary files try: - for fname in self.extra_filenames: + for fname in self.extra_filenames: os.remove( fname ) if self.working_directory is not None: shutil.rmtree( self.working_directory ) + if self.app.config.set_metadata_externally: + self.external_output_metadata.cleanup_external_metadata() except: log.exception( "Unable to cleanup job %d" % self.job_id ) @@ -573,7 +590,15 @@ class JobWrapper( object ): for outfile in [ str( o ) for o in output_paths ]: sizes.append( ( outfile, os.stat( outfile ).st_size ) ) return sizes - + def setup_external_metadata( self, exec_dir = None, tmp_dir = None, dataset_files_path = None, **kwds ): + if tmp_dir is None: + #this dir should should relative to the exec_dir + tmp_dir = self.app.config.new_file_path + if dataset_files_path is None: + dataset_files_path = self.app.model.Dataset.file_path + job = model.Job.get( self.job_id ) + return self.external_output_metadata.setup_external_metadata( [ output_dataset_assoc.dataset for output_dataset_assoc in job.output_datasets ], exec_dir = exec_dir, tmp_dir = tmp_dir, dataset_files_path = dataset_files_path, **kwds ) + class DefaultJobDispatcher( object ): def __init__( self, app ): self.app = app diff --git a/lib/galaxy/jobs/runners/local.py b/lib/galaxy/jobs/runners/local.py index a2d675a70b6..7d07a04a0da 100644 --- a/lib/galaxy/jobs/runners/local.py +++ b/lib/galaxy/jobs/runners/local.py @@ -99,6 +99,21 @@ class LocalJobRunner( object ): job_wrapper.fail( "failure running job", exception=True ) log.exception("failure running job %d" % job_wrapper.job_id) return + + #run the metadata setting script here + #this is terminatable when output dataset/job is deleted + #so that long running set_meta()s can be cancelled without having to reboot the server + if job_wrapper.get_state() not in [ model.Job.states.ERROR, model.Job.states.DELETED ] and self.app.config.set_metadata_externally: + external_metadata_script = job_wrapper.setup_external_metadata( kwds = { 'overwrite' : False } ) #we don't want to overwrite metadata that was copied over in init_meta(), as per established behavior + log.debug( 'executing external set_meta script for job %d: %s' % ( job_wrapper.job_id, external_metadata_script ) ) + external_metadata_proc = subprocess.Popen( args = external_metadata_script, + shell = True, + env = env, + preexec_fn = os.setpgrp ) + job_wrapper.external_output_metadata.set_job_runner_external_pid( external_metadata_proc.pid ) + external_metadata_proc.wait() + log.debug( 'execution of external set_meta finished for job %d' % job_wrapper.job_id ) + # Finish the job try: job_wrapper.finish( stdout, stderr ) @@ -131,12 +146,17 @@ class LocalJobRunner( object ): return False def stop_job( self, job ): - if job.job_runner_external_id is None: + #if our local job has JobExternalOutputMetadata associated, then our primary job has to have already finished + if job.external_output_metadata: + pid = job.external_output_metadata[0].job_runner_external_pid #every JobExternalOutputMetadata has a pid set, we just need to take from one of them + else: + pid = job.job_runner_external_id + if pid in [ None, '' ]: log.warning( "stop_job(): %s: no PID in database for job, unable to stop" % job.id ) return - pid = int( job.job_runner_external_id ) + pid = int( pid ) if not self.check_pid( pid ): - log.warning( "stop_job(): %s: PID %d was already dead or can't be signaled" %job.id ) + log.warning( "stop_job(): %s: PID %d was already dead or can't be signaled" % ( job.id, pid ) ) return for sig in [ 15, 9 ]: try: diff --git a/lib/galaxy/jobs/runners/pbs.py b/lib/galaxy/jobs/runners/pbs.py index 91c0044effc..257ca66b4de 100644 --- a/lib/galaxy/jobs/runners/pbs.py +++ b/lib/galaxy/jobs/runners/pbs.py @@ -27,6 +27,7 @@ if [ "$GALAXY_LIB" != "None" ]; then fi cd %s %s +%s """ pbs_symlink_template = """#!/bin/sh @@ -208,7 +209,11 @@ class PBSJobRunner( object ): if self.app.config.pbs_stage_path != '': script = pbs_symlink_template % (job_wrapper.galaxy_lib_dir, " ".join(job_wrapper.get_input_fnames() + job_wrapper.get_output_fnames()), self.app.config.pbs_stage_path, exec_dir, command_line) else: - script = pbs_template % (job_wrapper.galaxy_lib_dir, exec_dir, command_line) + if self.app.config.set_metadata_externally: + external_metadata_script = job_wrapper.setup_external_metadata( exec_dir = exec_dir, tmp_dir = self.app.config.new_file_path, dataset_files_path = self.app.model.Dataset.file_path, kwds = { 'overwrite' : False } ) #we don't want to overwrite metadata that was copied over in init_meta(), as per established behavior + else: + external_metadata_script = "" + script = pbs_template % ( job_wrapper.galaxy_lib_dir, exec_dir, command_line, external_metadata_script ) job_file = "%s/%s.sh" % (self.app.config.cluster_files_directory, job_wrapper.job_id) fh = file(job_file, "w") fh.write(script) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 598e443b8fa..70a28e80766 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -141,6 +141,21 @@ class JobToOutputDatasetAssociation( object ): self.name = name self.dataset = dataset +class JobExternalOutputMetadata( object ): + def __init__( self, job = None, dataset = None ): + self.job = job + if isinstance( dataset, galaxy.model.HistoryDatasetAssociation ): + self.history_dataset_association = dataset + elif isinstance( dataset, galaxy.model.LibraryDatasetDatasetAssociation ): + self.library_dataset_dataset_association = dataset + @property + def dataset( self ): + if self.history_dataset_association: + return self.history_dataset_association + elif self.library_dataset_dataset_association: + return self.library_dataset_dataset_association + return None + class Group( object ): def __init__( self, name = None ): self.name = name @@ -162,7 +177,6 @@ class History( object ): self.user = user self.datasets = [] self.galaxy_sessions = [] - def _next_hid( self ): # TODO: override this with something in the database that ensures # better integrity @@ -174,16 +188,14 @@ class History( object ): if dataset.hid > last_hid: last_hid = dataset.hid return last_hid + 1 - def add_galaxy_session( self, galaxy_session, association=None ): if association is None: self.galaxy_sessions.append( GalaxySessionToHistoryAssociation( galaxy_session, self ) ) else: self.galaxy_sessions.append( association ) - def add_dataset( self, dataset, parent_id=None, genome_build=None, set_hid = True ): if isinstance( dataset, Dataset ): - dataset = HistoryDatasetAssociation( dataset = dataset ) + dataset = HistoryDatasetAssociation( dataset = dataset, copied_from = dataset ) dataset.flush() elif not isinstance( dataset, HistoryDatasetAssociation ): raise TypeError, "You can only add Dataset and HistoryDatasetAssociation instances to a history ( you tried to add %s )." % str( dataset ) @@ -198,31 +210,26 @@ class History( object ): else: if set_hid: dataset.hid = self._next_hid() + dataset.history = self if genome_build not in [None, '?']: self.genome_build = genome_build self.datasets.append( dataset ) - - def copy(self): - des = History() + def copy( self, target_user = None ): + if not target_user: + target_user = self.user + des = History( user = target_user ) des.flush() des.name = self.name - des.user_id = self.user_id for data in self.datasets: - new_data = data.copy( copy_children = True ) - des.add_dataset( new_data ) + new_data = data.copy( copy_children = True, target_history = des ) + des.add_dataset( new_data, set_hid = False ) new_data.flush() des.hid_counter = self.hid_counter des.flush() return des - -# class Query( object ): -# def __init__( self, name=None, state=None, tool_parameters=None, history=None ): -# self.name = name or "Unnamed query" -# self.state = state -# self.tool_parameters = tool_parameters -# # Relationships -# self.history = history -# self.datasets = [] + @property + def activatable_datasets( self ): + return [ hda for hda in self.datasets if not hda.dataset.purged ] #this needs to be a list class UserRoleAssociation( object ): def __init__( self, user, role ): @@ -341,7 +348,6 @@ class Dataset( object ): self.external_filename = external_filename self._extra_files_path = extra_files_path self.file_size = file_size - def get_file_name( self ): if not self.external_filename: assert self.id is not None, "ID must be set before filename used (commit the object)" @@ -364,15 +370,12 @@ class Dataset( object ): filename = self.external_filename # Make filename absolute return os.path.abspath( filename ) - def set_file_name ( self, filename ): if not filename: self.external_filename = None else: self.external_filename = filename - file_name = property( get_file_name, set_file_name ) - @property def extra_files_path( self ): if self._extra_files_path: @@ -384,7 +387,6 @@ class Dataset( object ): path = os.path.join( os.path.join( self.file_path, *directory_hash_id( self.id ) ), "dataset_%d_files" % self.id ) # Make path absolute return os.path.abspath( path ) - def get_size( self ): """Returns the size of the data on disk""" if self.file_size: @@ -406,7 +408,6 @@ class Dataset( object ): return self.get_size() > 0 def mark_deleted( self, include_children=True ): self.deleted = True - # FIXME: sqlalchemy will replace this def _delete(self): """Remove the file that corresponds to this data""" @@ -436,7 +437,7 @@ class DatasetInstance( object ): self.visible = visible # Relationships if not dataset and create_dataset: - dataset = Dataset() + dataset = Dataset( state=Dataset.states.NEW ) dataset.flush() self.dataset = dataset self.parent_id = parent_id @@ -474,7 +475,6 @@ class DatasetInstance( object ): def get_dbkey( self ): dbkey = self.metadata.dbkey if not isinstance(dbkey, list): dbkey = [dbkey] - #if dbkey in [["?"], [None], []]: dbkey = [self.old_dbkey] if dbkey in [[None], []]: return "?" return dbkey[0] def set_dbkey( self, value ): @@ -483,10 +483,6 @@ class DatasetInstance( object ): self.metadata.dbkey = [value] else: self.metadata.dbkey = value - #if isinstance(value, list): - # self.old_dbkey = value[0] - #else: - # self.old_dbkey = value dbkey = property( get_dbkey, set_dbkey ) def change_datatype( self, new_ext ): self.clear_associated_files() @@ -567,7 +563,6 @@ class DatasetInstance( object ): if self.purged: return False return True - @property def source_library_dataset( self ): def get_source( dataset ): @@ -599,7 +594,7 @@ class HistoryDatasetAssociation( DatasetInstance ): self.copied_from_history_dataset_association = copied_from_history_dataset_association self.copied_from_library_dataset_dataset_association = copied_from_library_dataset_dataset_association def copy( self, copy_children = False, parent_id = None, target_history = None ): - des = HistoryDatasetAssociation( hid=self.hid, + hda = HistoryDatasetAssociation( hid=self.hid, name=self.name, info=self.info, blurb=self.blurb, @@ -612,133 +607,71 @@ class HistoryDatasetAssociation( DatasetInstance ): parent_id=parent_id, copied_from_history_dataset_association=self, history = target_history ) - des.flush() - des.set_size() - des.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id + hda.flush() + hda.set_size() + hda.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id if copy_children: for child in self.children: - child_copy = child.copy( copy_children = copy_children, parent_id = des.id ) + child_copy = child.copy( copy_children = copy_children, parent_id = hda.id ) if not self.datatype.copy_safe_peek: - des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs - des.flush() - return des - - def to_library_dataset_dataset_association( self, parent_id = None, target_folder = None ): - des = LibraryDatasetDatasetAssociation( name=self.name, - info=self.info, - blurb=self.blurb, - peek=self.peek, - extension=self.extension, - dbkey=self.dbkey, - dataset = self.dataset, - visible=self.visible, - deleted=self.deleted, - parent_id=parent_id, - copied_from_history_dataset_association = self, - #folder = target_folder - ) - des.flush() - des.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id - if target_folder: - new_data = LibraryDataset( library_dataset_dataset_association = des ) - target_folder.add_dataset( new_data ) - new_data.flush() + hda.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs + hda.flush() + return hda + def to_library_dataset_dataset_association( self, target_folder, parent_id=None ): + # Create e new LibraryDataset + library_dataset = LibraryDataset( folder=target_folder, name=self.name, info=self.info ) + library_dataset.flush() + ldda = LibraryDatasetDatasetAssociation( name=self.name, + info=self.info, + blurb=self.blurb, + peek=self.peek, + extension=self.extension, + dbkey=self.dbkey, + dataset=self.dataset, + library_dataset=library_dataset, + visible=self.visible, + deleted=self.deleted, + parent_id=parent_id, + copied_from_history_dataset_association=self ) + ldda.flush() + # Must set metadata after flushed, as MetadataFiles require dataset.id + ldda.metadata = self.metadata + library_dataset.library_dataset_dataset_association_id = ldda.id + library_dataset.flush() + target_folder.add_library_dataset( library_dataset, genome_build=ldda.dbkey ) for child in self.children: - child_copy = child.to_library_dataset_dataset_association( parent_id = des.id ) + child_copy = child.to_library_dataset_dataset_association( target_folder=target_folder, parent_id=ldda.id ) if not self.datatype.copy_safe_peek: - des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs - des.flush() - return des - + ldda.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs + ldda.flush() + return ldda def clear_associated_files( self, metadata_safe = False, purge = False ): #metadata_safe = True means to only clear when assoc.metadata_safe == False for assoc in self.implicitly_converted_datasets: if not metadata_safe or not assoc.metadata_safe: assoc.clear( purge = purge ) -class History( object ): - def __init__( self, id=None, name=None, user=None ): - self.id = id - self.name = name or "Unnamed history" - self.deleted = False - self.purged = False - self.genome_build = None - # Relationships - self.user = user - self.datasets = [] - self.galaxy_sessions = [] - def _next_hid( self ): - # TODO: override this with something in the database that ensures - # better integrity - if len( self.datasets ) == 0: - return 1 - else: - last_hid = 0 - for dataset in self.datasets: - if dataset.hid > last_hid: - last_hid = dataset.hid - return last_hid + 1 - def add_galaxy_session( self, galaxy_session, association=None ): - if association is None: - self.galaxy_sessions.append( GalaxySessionToHistoryAssociation( galaxy_session, self ) ) - else: - self.galaxy_sessions.append( association ) - def add_dataset( self, dataset, parent_id=None, genome_build=None, set_hid = True ): - if isinstance( dataset, Dataset ): - dataset = HistoryDatasetAssociation( dataset = dataset, copied_from = dataset ) - dataset.flush() - elif not isinstance( dataset, HistoryDatasetAssociation ): - raise TypeError, "You can only add Dataset and HistoryDatasetAssociation instances to a history." - if parent_id: - for data in self.datasets: - if data.id == parent_id: - dataset.hid = data.hid - break - else: - if set_hid: dataset.hid = self._next_hid() - else: - if set_hid: dataset.hid = self._next_hid() - dataset.history = self - if genome_build not in [None, '?']: - self.genome_build = genome_build - self.datasets.append( dataset ) - def copy( self, target_user = None ): - if not target_user: - target_user = self.user - des = History( user = target_user ) - des.flush() - des.name = self.name - for data in self.datasets: - new_data = data.copy( copy_children = True, target_history = des ) - des.add_dataset( new_data, set_hid = False ) - new_data.flush() - des.hid_counter = self.hid_counter - des.flush() - return des - class Library( object ): permitted_actions = get_permitted_actions( filter='LIBRARY' ) def __init__( self, name = None, description = None, root_folder = None ): self.name = name or "Unnamed library" self.description = description self.root_folder = root_folder - - def get_library_item_info_templates( self, template_list = [] ): + def get_library_item_info_templates( self, template_list=[], restrict=False ): if self.library_info_template_associations: template_list.extend( [ lita.library_item_info_template for lita in self.library_info_template_associations if lita.library_item_info_template not in template_list ] ) return template_list class LibraryFolder( object ): - def __init__( self, name = None, description = None, item_count = 0, order_id = None ): + def __init__( self, name=None, description=None, item_count=0, order_id=None ): self.name = name or "Unnamed folder" self.description = description self.item_count = item_count self.order_id = order_id self.genome_build = None - def add_dataset( self, dataset, genome_build=None ): - #this should create a LibraryDataset if ldda is passed - dataset.folder_id = self.id - dataset.order_id = self.item_count + def add_library_dataset( self, library_dataset, genome_build=None ): + library_dataset.folder_id = self.id + library_dataset.order_id = self.item_count self.item_count += 1 if genome_build not in [None, '?']: self.genome_build = genome_build @@ -746,140 +679,123 @@ class LibraryFolder( object ): folder.parent_id = self.id folder.order_id = self.item_count self.item_count += 1 - - def get_library_item_info_templates( self, template_list = [] ): + def get_library_item_info_templates( self, template_list=[], restrict=False ): + # If restrict is True, we'll return only those templates directly associated with this Folder if self.library_folder_info_template_associations: template_list.extend( [ lfita.library_item_info_template for lfita in self.library_folder_info_template_associations if lfita.library_item_info_template not in template_list ] ) - if self.parent: + if restrict not in [ 'True', True ] and self.parent: self.parent.get_library_item_info_templates( template_list ) - elif self.library_root: + elif restrict not in [ 'True', True, 'folder' ] and self.library_root: for library_root in self.library_root: library_root.get_library_item_info_templates( template_list ) return template_list - @property def active_components( self ): return list( self.active_folders ) + list( self.active_datasets ) class LibraryDataset( object ): - #This class acts as a proxy to the currently selected LDDA - def __init__( self, - folder = None, - order_id = None, - name = None, - info = None, - library_dataset_dataset_association = None, - create_dataset=False, - **kwd - ): + # This class acts as a proxy to the currently selected LDDA + def __init__( self, folder=None, order_id=None, name=None, info=None, library_dataset_dataset_association=None, **kwd ): self.folder = folder self.order_id = order_id self.name = name self.info = info - if create_dataset and not library_dataset_dataset_association: - #self.flush() #we need to flush self, so that the ldda will have a ld id to point to - library_dataset_dataset_association = LibraryDatasetDatasetAssociation( name=name, info=info, create_dataset=create_dataset, **kwd ) self.library_dataset_dataset_association = library_dataset_dataset_association - - def set_library_dataset_dataset_association( self, dataset ): - self.library_dataset_dataset_association = dataset - dataset.library_dataset = self - dataset.flush() + def set_library_dataset_dataset_association( self, ldda ): + self.library_dataset_dataset_association = ldda + ldda.library_dataset = self + ldda.flush() self.flush() - def get_info( self ): - # Use info from ldfa - return self.library_dataset_dataset_association.info + if self.library_dataset_dataset_association: + return self.library_dataset_dataset_association.info + elif self._info: + return self._info + else: + return 'no info' def set_info( self, info ): self._info = info info = property( get_info, set_info ) - def get_name( self ): - # Use info from ldda - return self.library_dataset_dataset_association.name + if self.library_dataset_dataset_association: + return self.library_dataset_dataset_association.name + elif self._name: + return self._name + else: + return 'Unnamed dataset' def set_name( self, name ): self._name = name name = property( get_name, set_name ) - def display_name( self ): - # Use name from ldda is versioned info is not set - if self._name: - return self.datatype.display_name( self ) self.library_dataset_dataset_association.display_name() - - def __getattr__( self, name ): - return getattr( self.library_dataset_dataset_association, name ) #Any nonexistant attributes will be pulled from the ldda - - def get_library_item_info_templates( self, template_list = [] ): + def get_library_item_info_templates( self, template_list=[], restrict=False ): + # If restrict is True, we'll return only those templates directly associated with this LibraryDataset if self.library_dataset_info_template_associations: template_list.extend( [ ldita.library_item_info_template for ldita in self.library_dataset_info_template_associations if ldita.library_item_info_template not in template_list ] ) - self.folder.get_library_item_info_templates( template_list ) + if restrict not in [ 'True', True ]: + self.folder.get_library_item_info_templates( template_list, restrict ) return template_list class LibraryDatasetDatasetAssociation( DatasetInstance ): - def __init__( self, - copied_from_history_dataset_association = None, - copied_from_library_dataset_dataset_association = None, - library_dataset = None, - **kwd ): + def __init__( self, copied_from_history_dataset_association=None, copied_from_library_dataset_dataset_association=None, library_dataset=None, **kwd ): DatasetInstance.__init__( self, **kwd ) - self.library_dataset = library_dataset self.copied_from_history_dataset_association = copied_from_history_dataset_association self.copied_from_library_dataset_dataset_association = copied_from_library_dataset_dataset_association - def to_history_dataset_association( self, parent_id = None, target_history = None ): - if target_history: - hid = target_history._next_hid() - else: - hid = None - des = HistoryDatasetAssociation( name=self.name, - info=self.info, + self.library_dataset = library_dataset + def to_history_dataset_association( self, target_history, parent_id=None ): + hid = target_history._next_hid() + hda = HistoryDatasetAssociation( name=self.name, + info=self.info, blurb=self.blurb, peek=self.peek, extension=self.extension, dbkey=self.dbkey, - dataset = self.dataset, + dataset=self.dataset, visible=self.visible, deleted=self.deleted, parent_id=parent_id, - copied_from_library_dataset_dataset_association = self, - history = target_history, - hid = hid ) - des.flush() - des.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id + copied_from_library_dataset_dataset_association=self, + history=target_history, + hid=hid ) + hda.flush() + hda.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id for child in self.children: - child_copy = child.to_history_dataset_association( parent_id = des.id ) + child_copy = child.to_history_dataset_association( target_history=target_history, parent_id=hda.id ) if not self.datatype.copy_safe_peek: - des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs - des.flush() - return des + hda.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs + hda.flush() + return hda def copy( self, copy_children = False, parent_id = None, target_folder = None ): - des = LibraryDatasetDatasetAssociation( name=self.name, - info=self.info, - blurb=self.blurb, - peek=self.peek, - extension=self.extension, - dbkey=self.dbkey, - dataset = self.dataset, - visible=self.visible, - deleted=self.deleted, - parent_id=parent_id, - copied_from_library_dataset_dataset_association = self, - folder = target_folder ) - des.flush() - des.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id + ldda = LibraryDatasetDatasetAssociation( name=self.name, + info=self.info, + blurb=self.blurb, + peek=self.peek, + extension=self.extension, + dbkey=self.dbkey, + dataset=self.dataset, + visible=self.visible, + deleted=self.deleted, + parent_id=parent_id, + copied_from_library_dataset_dataset_association=self, + folder=target_folder ) + ldda.flush() + # Need to set after flushed, as MetadataFiles require dataset.id + ldda.metadata = self.metadata if copy_children: for child in self.children: - child_copy = child.copy( copy_children = copy_children, parent_id = des.id ) + child_copy = child.copy( copy_children = copy_children, parent_id = ldda.id ) if not self.datatype.copy_safe_peek: - des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs - des.flush() - return des + # In some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs + ldda.set_peek() + ldda.flush() + return ldda def clear_associated_files( self, metadata_safe = False, purge = False ): return - def get_library_item_info_templates( self, template_list = [] ): + def get_library_item_info_templates( self, template_list=[], restrict=False ): + # If restrict is True, we'll return only those templates directly associated with this LibraryDatasetDatasetAssociation if self.library_dataset_dataset_info_template_associations: template_list.extend( [ lddita.library_item_info_template for lddita in self.library_dataset_dataset_info_template_associations if lddita.library_item_info_template not in template_list ] ) - self.library_dataset.get_library_item_info_templates( template_list ) + self.library_dataset.get_library_item_info_templates( template_list, restrict ) return template_list class LibraryInfoTemplateAssociation( object ): @@ -909,45 +825,38 @@ class LibraryItemInfoTemplate( object ): new_elem.flush() return new_elem - """class InfoField( object ): - def __init__( self, name, description, type ): - self.name = name - self.description = description - self.type = type - class StringInfoField( InfoField ): - def __init__( self, name, description, type = 'string' ): - InfoField.__init__( self, name, description, type ) - def __init__( self, name='unnamed', contents=None ): - self.contents = contents - """ class LibraryItemInfoTemplateElement( object ): pass class LibraryInfoAssociation( object ): - def set_library_item( self, library_item ): + def set_library_item( self, library_item, user ): if isinstance( library_item, Library ): self.library = library_item + self.user = user else: raise "Invalid Library specified: %s" % library_item.__class__.__name__ class LibraryFolderInfoAssociation( object ): - def set_library_item( self, library_item ): + def set_library_item( self, library_item, user ): if isinstance( library_item, LibraryFolder ): self.folder = library_item + self.user = user else: raise "Invalid Library specified: %s" % library_item.__class__.__name__ class LibraryDatasetInfoAssociation( object ): - def set_library_item( self, library_item ): + def set_library_item( self, library_item, user ): if isinstance( library_item, LibraryDataset ): self.library_dataset = library_item + self.user = user else: raise "Invalid Library specified: %s" % library_item.__class__.__name__ class LibraryDatasetDatasetInfoAssociation( object ): - def set_library_item( self, library_item ): + def set_library_item( self, library_item, user ): if isinstance( library_item, LibraryDatasetDatasetAssociation ): self.library_dataset_dataset_association = library_item + self.user = user else: raise "Invalid Library specified: %s" % library_item.__class__.__name__ @@ -960,16 +869,6 @@ class LibraryItemInfo( object ): class LibraryItemInfoElement( object ): pass - -# class Query( object ): -# def __init__( self, name=None, state=None, tool_parameters=None, history=None ): -# self.name = name or "Unnamed query" -# self.state = state -# self.tool_parameters = tool_parameters -# # Relationships -# self.history = history -# self.datasets = [] - class ValidationError( object ): def __init__( self, message=None, err_type=None, attributes=None ): diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 3a128e65cb0..d416e00acd4 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -58,13 +58,6 @@ History.table = Table( "history", metadata, Column( "purged", Boolean, index=True, default=False ), Column( "genome_build", TrimmedString( 40 ) ) ) -# model.Query.table = Table( "query", engine, -# Column( "id", Integer, primary_key=True), -# Column( "history_id", Integer, ForeignKey( "history.id" ) ), -# Column( "name", String( 255 ) ), -# Column( "state", String( 64 ) ), -# Column( "tool_parameters", Pickle() ) ) - HistoryDatasetAssociation.table = Table( "history_dataset_association", metadata, Column( "id", Integer, primary_key=True ), Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ), @@ -228,10 +221,8 @@ LibraryDataset.table = Table( "library_dataset", metadata, Column( "update_time", DateTime, default=now, onupdate=now ), Column( "name", TrimmedString( 255 ), key="_name" ), #when not None/null this will supercede display in library (but not when imported into user's history?) Column( "info", TrimmedString( 255 ), key="_info" ), #when not None/null this will supercede display in library (but not when imported into user's history?) - Column( "deleted", Boolean, index=True, default=False ), - ) + Column( "deleted", Boolean, index=True, default=False ) ) -#this should be renamed, no longer an association between dataset and folder LibraryDatasetDatasetAssociation.table = Table( "library_dataset_dataset_association", metadata, Column( "id", Integer, primary_key=True ), Column( "library_dataset_id", Integer, ForeignKey( "library_dataset.id" ), index=True ), @@ -329,7 +320,7 @@ LibraryItemInfoElement.table = Table( "library_item_info_element", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), - Column( "contents", TEXT ), + Column( "contents", JSONType() ), Column( "library_item_info_id", Integer, ForeignKey( "library_item_info.id" ), index=True ), Column( "library_item_info_template_element_id", Integer, ForeignKey( "library_item_info_template_element.id" ), index=True ) ) @@ -411,6 +402,17 @@ JobToOutputDatasetAssociation.table = Table( "job_to_output_dataset", metadata, Column( "dataset_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ), Column( "name", String(255) ) ) +JobExternalOutputMetadata.table = Table( "job_external_output_metadata", metadata, + Column( "id", Integer, primary_key=True ), + Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ), + Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True, nullable=True ), + Column( "library_dataset_dataset_association_id", Integer, ForeignKey( "library_dataset_dataset_association.id" ), index=True, nullable=True ), + Column( "filename_in", String( 255 ) ), + Column( "filename_out", String( 255 ) ), + Column( "filename_results_code", String( 255 ) ), + Column( "filename_kwds", String( 255 ) ), + Column( "job_runner_external_pid", String( 255 ) ) ) + Event.table = Table( "event", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), @@ -567,8 +569,7 @@ assign_mapper( context, ImplicitlyConvertedDatasetAssociation, ImplicitlyConvert assign_mapper( context, History, History.table, properties=dict( galaxy_sessions=relation( GalaxySessionToHistoryAssociation ), datasets=relation( HistoryDatasetAssociation, backref="history", order_by=asc(HistoryDatasetAssociation.table.c.hid) ), - active_datasets=relation( HistoryDatasetAssociation, primaryjoin=( ( HistoryDatasetAssociation.table.c.history_id == History.table.c.id ) & ( not_( HistoryDatasetAssociation.table.c.deleted ) ) ), order_by=asc( HistoryDatasetAssociation.table.c.hid ), lazy=False, viewonly=True ), - activatable_datasets=relation( HistoryDatasetAssociation, primaryjoin=( ( HistoryDatasetAssociation.table.c.history_id == History.table.c.id ) & ( not_( Dataset.table.c.purged ) ) ), order_by=asc( HistoryDatasetAssociation.table.c.hid ), lazy=True, viewonly=True ) + active_datasets=relation( HistoryDatasetAssociation, primaryjoin=( ( HistoryDatasetAssociation.table.c.history_id == History.table.c.id ) & ( not_( HistoryDatasetAssociation.table.c.deleted ) ) ), order_by=asc( HistoryDatasetAssociation.table.c.hid ), lazy=False, viewonly=True ) ) ) assign_mapper( context, User, User.table, @@ -790,12 +791,18 @@ assign_mapper( context, JobToOutputDatasetAssociation, JobToOutputDatasetAssocia assign_mapper( context, JobParameter, JobParameter.table ) +assign_mapper( context, JobExternalOutputMetadata, JobExternalOutputMetadata.table, + properties=dict( job = relation( Job ), + history_dataset_association = relation( HistoryDatasetAssociation, lazy = False ), + library_dataset_dataset_association = relation( LibraryDatasetDatasetAssociation, lazy = False ) ) ) + assign_mapper( context, Job, Job.table, properties=dict( galaxy_session=relation( GalaxySession ), history=relation( History ), parameters=relation( JobParameter, lazy=False ), input_datasets=relation( JobToInputDatasetAssociation, lazy=False ), - output_datasets=relation( JobToOutputDatasetAssociation, lazy=False ) ) ) + output_datasets=relation( JobToOutputDatasetAssociation, lazy=False ), + external_output_metadata = relation( JobExternalOutputMetadata, lazy = False ) ) ) assign_mapper( context, Event, Event.table, properties=dict( history=relation( History ), diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index 3b321c7313a..6bd82f48e64 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -219,13 +219,6 @@ class GalaxyRBACAgent( RBACAgent ): dup.flush() if history: for history in user.active_histories: - """ - TODO: instead of user.active_histories, a faster approach would be the following, but we need the - history.activatable_datasets mapper corrected so that eagerload can be used. - histories = app.model.History.filter( and_( app.model.History.table.c.user_id==user.id, - app.model.History.table.c.purged==False ) ) \ - .options( eagerload( 'activatable_datasets' ) ).all() - """ self.history_set_default_permissions( history, permissions=permissions, dataset=dataset, bypass_manage_permission=bypass_manage_permission ) def user_get_default_permissions( self, user ): permissions = {} @@ -352,7 +345,6 @@ class GalaxyRBACAgent( RBACAgent ): self.set_dataset_permission( dataset, { self.permitted_actions.DATASET_ACCESS : [ sharing_role ] } ) def set_all_library_permissions( self, library_item, permissions={} ): # Set new permissions on library_item, eliminating all current permissions - # TODO: Make sure the following should still be library_item.actions for role_assoc in library_item.actions: role_assoc.delete() role_assoc.flush() @@ -364,6 +356,19 @@ class GalaxyRBACAgent( RBACAgent ): action = action.action for role_assoc in [ permission_class( action, library_item, role ) for role in roles ]: role_assoc.flush() + def get_library_dataset_permissions( self, library_dataset ): + # Permissions will always be the same for LibraryDatasets and associated + # LibraryDatasetDatasetAssociations + if isinstance( library_dataset, self.model.LibraryDatasetDatasetAssociation ): + library_dataset = library_dataset.library_dataset + permissions = {} + for library_dataset_permission in library_dataset.actions: + action = self.get_action( library_dataset_permission.action ) + if action in permissions: + permissions[ action ].append( library_dataset_permission.role ) + else: + permissions[ action ] = [ library_dataset_permission.role ] + return permissions def copy_library_permissions( self, source_library_item, target_library_item, user=None ): # Copy all permissions from source permissions = {} @@ -452,16 +457,16 @@ class GalaxyRBACAgent( RBACAgent ): """ if isinstance( entry, self.model.Library ): return self.check_folder_contents( user, entry.root_folder ) - elif isinstance( entry, self.model.LibraryDatasetDatasetAssociation ): - return self.allow_action( user, self.permitted_actions.DATASET_ACCESS, dataset=entry ) elif isinstance( entry, self.model.LibraryFolder ): - for dataset in entry.active_datasets: - if self.allow_action( user, self.permitted_actions.DATASET_ACCESS, dataset=dataset ): + for library_dataset in entry.active_datasets: + if self.allow_action( user, self.permitted_actions.DATASET_ACCESS, dataset=library_dataset.library_dataset_dataset_association.dataset ): return True for folder in entry.active_folders: if self.check_folder_contents( user, folder ): return True return False + elif isinstance( entry, self.model.LibraryDatasetDatasetAssociation ): + return self.allow_action( user, self.permitted_actions.DATASET_ACCESS, dataset=entry.dataset ) else: raise 'Passed an illegal object to check_folder_contents: %s' % type( entry ) diff --git a/lib/galaxy/tools/actions/upload.py b/lib/galaxy/tools/actions/upload.py index f0a05835bf7..8d293dcd5af 100644 --- a/lib/galaxy/tools/actions/upload.py +++ b/lib/galaxy/tools/actions/upload.py @@ -83,11 +83,13 @@ class UploadToolAction( object ): else: INFO = "uploaded url" url_paste = url_paste.replace( '\r', '' ).split( '\n' ) + name_set_from_line = False #if we are setting the name from the line, it needs to be the line that creates that dataset for line in url_paste: line = line.rstrip( '\r\n' ) if line: - if not NAME: + if not NAME or name_set_from_line: NAME = line + name_set_from_line = True try: temp_name = sniff.stream_to_file( urllib.urlopen( line ), prefix='url_paste' ) except Exception, e: @@ -126,7 +128,8 @@ class UploadToolAction( object ): return self.upload_empty( trans, job, "Empty file error:", "you attempted to upload an empty file." ) elif len( data_list ) < 1: return self.upload_empty( trans, job, "No data error:", "either you pasted no data, the url you specified is invalid, or you have not specified a file." ) - hda = data_list[0] + #if we could make a 'real' job here, then metadata could be set before job.finish() is called + hda = data_list[0] #only our first hda is being added as input for the job, why? job.state = trans.app.model.Job.states.OK file_size_str = datatypes.data.nice_size( hda.dataset.file_size ) job.info = "%s, size: %s" % ( hda.info, file_size_str ) @@ -229,7 +232,6 @@ class UploadToolAction( object ): else: self.line_count = sniff.convert_newlines( temp_name ) if file_type == 'auto': - log.debug("In upload, in if file_type == 'auto':") ext = sniff.guess_ext( temp_name, sniff_order=trans.app.datatypes_registry.sniff_order ) else: ext = file_type diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py index 7a64793cdf5..b0adadbedff 100644 --- a/lib/galaxy/util/__init__.py +++ b/lib/galaxy/util/__init__.py @@ -387,6 +387,41 @@ def read_build_sites(filename): print "ERROR: Unable to read builds for site file %s" %filename return build_sites +def relpath( path, start = None ): + """Return a relative version of a path""" + #modified from python 2.6.1 source code + + #version 2.6+ has it built in, we'll use the 'official' copy + if sys.version_info[:2] >= ( 2, 6 ): + if start is not None: + return os.path.relpath( path, start ) + return os.path.relpath( path ) + + #we need to initialize some local parameters + curdir = os.curdir + pardir = os.pardir + sep = os.sep + commonprefix = os.path.commonprefix + join = os.path.join + if start is None: + start = curdir + + #below is the unedited (but formated) relpath() from posixpath.py of 2.6.1 + #this will likely not function properly on non-posix systems, i.e. windows + if not path: + raise ValueError( "no path specified" ) + + start_list = os.path.abspath( start ).split( sep ) + path_list = os.path.abspath( path ).split( sep ) + + # Work out how much of the filepath is shared by start and path. + i = len( commonprefix( [ start_list, path_list ] ) ) + + rel_list = [ pardir ] * ( len( start_list )- i ) + path_list[ i: ] + if not rel_list: + return curdir + return join( *rel_list ) + galaxy_root_path = os.path.join(__path__[0], "..","..","..") dbnames = read_dbnames( os.path.join( galaxy_root_path, "tool-data", "shared", "ucsc", "builds.txt" ) ) #this list is used in edit attributes and the upload tool ucsc_build_sites = read_build_sites( os.path.join( galaxy_root_path, "tool-data", "shared", "ucsc", "ucsc_build_sites.txt" ) ) #this list is used in history.tmpl diff --git a/lib/galaxy/web/controllers/admin.py b/lib/galaxy/web/controllers/admin.py index ed0dc5431ff..73249388e3f 100644 --- a/lib/galaxy/web/controllers/admin.py +++ b/lib/galaxy/web/controllers/admin.py @@ -1,7 +1,6 @@ import shutil, StringIO, operator, urllib, gzip, tempfile from galaxy import util, datatypes from galaxy.web.base.controller import * -from galaxy.web.controllers.dataset import upload_dataset from galaxy.model.orm import * import logging @@ -856,9 +855,10 @@ class Admin( BaseController ): root_folder.flush() library.root_folder = root_folder library.flush() - msg = 'The new library named %s has been created' % library.name + msg = "The new library named '%s' has been created" % library.name return trans.response.send_redirect( web.url_for( controller='admin', - action='browse_libraries', + action='browse_library', + id=library.id, msg=util.sanitize_text( msg ), messagetype='done' ) ) return trans.fill_template( '/admin/library/new_library.mako', msg=msg, messagetype=messagetype ) @@ -869,13 +869,13 @@ class Admin( BaseController ): new_description = util.restore_text( params.description ) if not new_name: msg = 'Enter a valid name' - return trans.fill_template( '/admin/library/library_info.mako', library=library, msg=msg, messagetype='error' ) + return trans.fill_template( '/admin/library/library_info.mako', + library=library, + restrict=params.get( 'restrict', False ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype='error' ) else: - if params.get( 'root_folder', False ): - root_folder = library.root_folder - root_folder.name = new_name - root_folder.flush() - root_folder.refresh() library.name = new_name library.description = new_description library.flush() @@ -886,21 +886,34 @@ class Admin( BaseController ): information=True, msg=util.sanitize_text( msg ), messagetype='done' ) ) - return trans.fill_template( '/admin/library/library_info.mako', library=library, msg=msg, messagetype=messagetype ) + return trans.fill_template( '/admin/library/library_info.mako', + library=library, + restrict=params.get( 'restrict', False ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) elif action == 'delete': # TODO: need to revamp the way we delete libraries, folders and contained LibraryDatasets. def delete_folder( library_folder ): - for folder in library_folder.active_folders: + library_folder.refresh() + for folder in library_folder.folders: delete_folder( folder ) - for ldda in library_folder.active_datasets: - # We don't set ldda.dataset.deleted to True here because the cleanup_dataset script - # will eventually remove it from disk. The purge_library method below sets the dataset - # to deleted. This allows for the library to be undeleted ( before it is purged ), - # restoring all of its contents. - ldda.deleted = True - ldda.flush() + for library_dataset in library_folder.datasets: + library_dataset.refresh() + ldda = library_dataset.library_dataset_dataset_association + if ldda: + ldda.refresh() + # We don't set ldda.dataset.deleted to True here because the cleanup_dataset script + # will eventually remove it from disk. The purge_library method below sets the dataset + # to deleted. This allows for the library to be undeleted ( before it is purged ), + # restoring all of its contents. + ldda.deleted = True + ldda.flush() + library_dataset.deleted = True + library_dataset.flush() library_folder.deleted = True library_folder.flush() + library.refresh() delete_folder( library.root_folder ) library.deleted = True library.flush() @@ -944,10 +957,18 @@ class Admin( BaseController ): library = trans.app.model.Library.get( int( params.id ) ) def undelete_folder( library_folder ): for folder in library_folder.folders: + folder.refresh() undelete_folder( folder ) - for ldda in library_folder.datasets: - ldda.deleted = False - ldda.flush() + library_folder.refresh() + for library_dataset in library_folder.datasets: + library_dataset.refresh() + ldda = library_dataset.library_dataset_dataset_association + if ldda: + ldda.refresh() + ldda.deleted = False + ldda.flush() + library_dataset.deleted = False + library_dataset.flush() library_folder.deleted = False library_folder.flush() undelete_folder( library.root_folder ) @@ -963,18 +984,25 @@ class Admin( BaseController ): def purge_folder( library_folder ): for lf in library_folder.folders: purge_folder( lf ) - for ldda in library_folder.datasets: - ldda.refresh() - dataset = ldda.dataset - dataset.refresh() - # If the dataset is not associated with any additional undeleted folders, then we can delete it. - # We don't set dataset.purged to True here because the cleanup_datasets script will do that for - # us, as well as removing the file from disk. - if not dataset.deleted and len( dataset.active_library_associations ) <= 1: # This is our current ldda + library_folder.refresh() + for library_dataset in library_folder.datasets: + library_dataset.refresh() + ldda = library_dataset.library_dataset_dataset_association + if ldda: + ldda.refresh() + dataset = ldda.dataset + dataset.refresh() + # If the dataset is not associated with any additional undeleted folders, then we can delete it. + # We don't set dataset.purged to True here because the cleanup_datasets script will do that for + # us, as well as removing the file from disk. + #if not dataset.deleted and len( dataset.active_library_associations ) <= 1: # This is our current ldda dataset.deleted = True dataset.flush() - ldda.deleted = True - ldda.flush() + ldda.deleted = True + ldda.flush() + library_dataset.deleted = True + library_dataset.flush() + library_folder.deleted = True library_folder.purged = True library_folder.flush() purge_folder( library.root_folder ) @@ -1000,7 +1028,7 @@ class Admin( BaseController ): else: # 'information' will be the default action = 'information' - folder = trans.app.model.LibraryFolder.get( id ) + folder = trans.app.model.LibraryFolder.get( int( id ) ) if not folder: msg = "Invalid folder specified, id: %s" % str( id ) return trans.response.send_redirect( web.url_for( controller='admin', @@ -1010,8 +1038,6 @@ class Admin( BaseController ): messagetype='error' ) ) if action == 'new': if params.new == 'submitted': - # Create the new folder, then return manage_folder template for new folder. - # New folders default to having the same permissions as their parent folder new_folder = trans.app.model.LibraryFolder( name=util.restore_text( params.name ), description=util.restore_text( params.description ) ) # We are associating the last used genome build with folders, so we will always @@ -1020,13 +1046,12 @@ class Admin( BaseController ): new_folder.genome_build = util.dbnames.default_value folder.add_folder( new_folder ) new_folder.flush() + # New folders default to having the same permissions as their parent folder trans.app.security_agent.copy_library_permissions( folder, new_folder, user=trans.get_user() ) msg = "New folder named '%s' has been added to the library" % new_folder.name return trans.response.send_redirect( web.url_for( controller='admin', - action='folder', - id=id, - library_id=library_id, - information=True, + action='browse_library', + id=library_id, msg=util.sanitize_text( msg ), messagetype='done' ) ) return trans.fill_template( '/admin/library/new_folder.mako', @@ -1044,6 +1069,8 @@ class Admin( BaseController ): return trans.fill_template( '/admin/library/folder_info.mako', folder=folder, library_id=library_id, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), msg=msg, messagetype='error' ) else: @@ -1061,6 +1088,8 @@ class Admin( BaseController ): return trans.fill_template( '/admin/library/folder_info.mako', folder=folder, library_id=library_id, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), msg=msg, messagetype=messagetype ) elif action == 'delete': @@ -1104,10 +1133,17 @@ class Admin( BaseController ): messagetype=messagetype ) @web.expose @web.require_admin - def library_dataset( self, trans, id, name=None, info=None, library_id=None, **kwd ): + def library_dataset( self, trans, id, library_id, **kwd ): params = util.Params( kwd ) msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) + if params.get( 'permissions', False ): + action = 'permissions' + elif params.get( 'versions', False ): + action = 'versions' + else: + # 'information' will be the default + action = 'information' library_dataset = trans.app.model.LibraryDataset.get( id ) if not library_dataset: msg = "Invalid library dataset specified, id: %s" %str( id ) @@ -1116,33 +1152,61 @@ class Admin( BaseController ): id=library_id, msg=util.sanitize_text( msg ), messagetype='error' ) ) - if params.get( 'edit_attributes_button', False ): - library_dataset.name = name - library_dataset.info = info - msg = 'Attributes updated for library dataset %s' % library_dataset.name - elif params.get( 'change_version', False ): - target_lda = trans.app.model.LibraryDatasetDatasetAssociation.get( kwd.get( 'set_lda_id' ) ) - library_dataset.library_dataset_dataset_association = target_lda - trans.app.model.flush() - msg = 'The current version of this library dataset has been updated to be %s' % target_lda.name - elif params.get( 'update_roles_button', False ): - # The user clicked the Save button on the 'Edit permissions and role associations' form - permissions = {} - for k, v in trans.app.model.Library.permitted_actions.items(): - in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ] - permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles - trans.app.security_agent.set_all_library_permissions( library_dataset, permissions ) - library_dataset.refresh() - msg = 'Permissions and roles have been updated for library dataset %s' % library_dataset.name - return trans.fill_template( "/admin/library/manage_library_dataset.mako", - library_dataset=library_dataset, - library_id=library_id, - msg=msg, - messagetype=messagetype ) + if action == 'information': + if params.get( 'edit_attributes_button', False ): + old_name = library_dataset.name + new_name = util.restore_text( params.get( 'name', '' ) ) + new_info = util.restore_text( params.get( 'info', '' ) ) + if not new_name: + msg = 'Enter a valid name' + messagetype = 'error' + else: + library_dataset.name = new_name + library_dataset.info = new_info + library_dataset.flush() + msg = "Dataset '%s' has been renamed to '%s'" % ( old_name, new_name ) + messagetype = 'done' + return trans.fill_template( '/admin/library/library_dataset_info.mako', + library_dataset=library_dataset, + library_id=library_id, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) + elif action == 'versions': + if params.get( 'change_version_button', False ): + target_lda = trans.app.model.LibraryDatasetDatasetAssociation.get( kwd.get( 'set_lda_id' ) ) + library_dataset.library_dataset_dataset_association = target_lda + trans.app.model.flush() + msg = 'The current version of this library dataset has been updated to be %s' % target_lda.name + return trans.fill_template( '/admin/library/library_dataset_versions.mako', + library_dataset=library_dataset, + library_id=library_id, + msg=msg, + messagetype=messagetype ) + elif action == 'permissions': + if params.get( 'update_roles_button', False ): + # The user clicked the Save button on the 'Edit permissions and role associations' form + permissions = {} + for k, v in trans.app.model.Library.permitted_actions.items(): + in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ] + permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles + # Set the LIBRARY permissions on the LibraryDataset + # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions + trans.app.security_agent.set_all_library_permissions( library_dataset, permissions ) + library_dataset.refresh() + # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation + trans.app.security_agent.set_all_library_permissions( library_dataset.library_dataset_dataset_association, permissions ) + library_dataset.library_dataset_dataset_association.refresh() + msg = 'Permissions and roles have been updated for library dataset %s' % library_dataset.name + return trans.fill_template( '/admin/library/library_dataset_permissions.mako', + library_dataset=library_dataset, + library_id=library_id, + msg=msg, + messagetype=messagetype ) @web.expose @web.require_admin - def library_dataset_dataset_association( self, trans, id=None, name="Unnamed", info='no info', - extension=None, folder_id=None, library_id=None, **kwd ): + def library_dataset_dataset_association( self, trans, library_id, folder_id, id=None, **kwd ): params = util.Params( kwd ) msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) @@ -1151,41 +1215,48 @@ class Admin( BaseController ): last_used_build = dbkey[0] else: last_used_build = dbkey - if folder_id and not last_used_build: + if folder_id: folder = trans.app.model.LibraryFolder.get( folder_id ) + else: + folder = None + if folder and not last_used_build: last_used_build = folder.genome_build - data_files = [] try: replace_dataset = trans.app.model.LibraryDataset.get( params.get( 'replace_id', None ) ) except: replace_dataset = None + # Let's not overwrite the imported datatypes module with the variable datatypes? + # The built-in 'id' is overwritten in lots of places as well + ldatatypes = [ x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys() ] + ldatatypes.sort() if params.get( 'new_dataset_button', False ): - created_ldda_ids = upload_dataset( trans, - controller='admin', - folder_id=folder_id, - replace_dataset=replace_dataset, - **kwd ) + created_ldda_ids = trans.webapp.controllers[ 'library_dataset' ].upload_dataset( trans, + controller='admin', + library_id=library_id, + folder_id=folder_id, + replace_dataset=replace_dataset, + **kwd ) if created_ldda_ids: total_added = len( created_ldda_ids.split( ',' ) ) - msg = "%i new datasets added to the library ( each is selected below ). " % total_added - msg += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary." - trans.response.send_redirect( web.url_for( controller='admin', - action='browse_library', - id=library_id, - created_ldda_ids=created_ldda_ids, - msg=util.sanitize_text( msg ), - messagetype='done' ) ) + if replace_dataset: + msg = "Added %d dataset versions to the library dataset '%s' in the folder '%s'." % ( total_added, replace_dataset.name, folder.name ) + else: + msg = "Added %d datasets to the library folder '%s' ( each is selected ). " % ( total_added, folder.name ) + msg += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary." + messagetype='done' else: msg = "Upload failed" - trans.response.send_redirect( web.url_for( controller='admin', - action='browse_library', - id=library_id, - created_ldda_ids=created_ldda_ids, - msg=util.sanitize_text( msg ), - messagetype='error' ) ) + messagetype='error' + trans.response.send_redirect( web.url_for( controller='admin', + action='browse_library', + id=library_id, + created_ldda_ids=created_ldda_ids, + msg=util.sanitize_text( msg ), + messagetype=messagetype ) ) elif not id or replace_dataset: - # No dataset(s) specified, so display the upload form. - # Send list of data formats to the form so the "extension" select list can be populated dynamically + upload_option = params.get( 'upload_option', 'upload_file' ) + # No dataset(s) specified, so display the upload form. Send list of data formats to the form + # so the "extension" select list can be populated dynamically file_formats = trans.app.datatypes_registry.upload_file_formats # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically def get_dbkey_options( last_used_build ): @@ -1194,10 +1265,11 @@ class Admin( BaseController ): dbkeys = get_dbkey_options( last_used_build ) # Send list of roles to the form so the dataset can be associated with 1 or more of them. roles = trans.app.model.Role.filter( trans.app.model.Role.table.c.deleted==False ).order_by( trans.app.model.Role.c.name ).all() - # Send the current history to enable importing datasets from history to library + # Send the current history to the form to enable importing datasets from history to library history = trans.get_history() history.refresh() return trans.fill_template( '/admin/library/new_dataset.mako', + upload_option=upload_option, library_id=library_id, folder_id=folder_id, file_formats=file_formats, @@ -1236,60 +1308,74 @@ class Admin( BaseController ): for k, v in trans.app.model.Dataset.permitted_actions.items(): in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ] permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles + # Set the DATASET permissions on the Dataset trans.app.security_agent.set_all_dataset_permissions( ldda.dataset, permissions ) - # Set/display library security info + ldda.dataset.refresh() permissions = {} for k, v in trans.app.model.Library.permitted_actions.items(): in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ] permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles + # Set the LIBRARY permissions on the LibraryDataset + # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions + trans.app.security_agent.set_all_library_permissions( ldda.library_dataset, permissions ) + ldda.library_dataset.refresh() + # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation trans.app.security_agent.set_all_library_permissions( ldda, permissions ) - ldda.dataset.refresh() + ldda.refresh() msg = "Permissions updated for dataset '%s'" % ldda.name - return trans.response.send_redirect( web.url_for( controller='admin', - action='library_dataset_dataset_association', - id=id, - permissions=True, - msg=util.sanitize_text( msg ), - messagetype='done' ) ) - return trans.fill_template( '/admin/library/ldda_permissions.mako', ldda=ldda, library_id=library_id, msg=msg, messagetype=messagetype ) + return trans.fill_template( '/admin/library/ldda_permissions.mako', + ldda=ldda, + library_id=library_id, + msg=msg, + messagetype=messagetype ) elif action == 'information': if params.get( 'change', False ): # The user clicked the Save button on the 'Change data type' form trans.app.datatypes_registry.change_datatype( ldda, params.datatype ) trans.app.model.flush() - msg = "Data type changed for dataset '%s'" % ldda.name - return trans.response.send_redirect( web.url_for( controller='admin', - action='library_dataset_dataset_association', - id=id, - library_id=library_id, - information=True, - msg=util.sanitize_text( msg ), - messagetype='done' ) ) + msg = "Data type changed for library dataset '%s'" % ldda.name + return trans.fill_template( "/admin/library/ldda_info.mako", + ldda=ldda, + library_id=library_id, + datatypes=ldatatypes, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) elif params.get( 'save', False ): # The user clicked the Save button on the 'Edit Attributes' form - ldda.name = name - ldda.info = info - # The following for loop will save all metadata_spec items - for name, spec in ldda.datatype.metadata_spec.items(): - if spec.get("readonly"): - continue - optional = params.get( "is_" + name, None ) - if optional and optional == 'true': - # optional element... == 'true' actually means it is NOT checked (and therefore ommitted) - setattr( ldda.metadata, name, None ) - else: - setattr( ldda.metadata, name, spec.unwrap( params.get ( name, None ) ) ) - ldda.metadata.dbkey = dbkey - ldda.datatype.after_edit( ldda ) - trans.app.model.flush() - msg = 'Attributes updated for dataset %s' % ldda.name - return trans.response.send_redirect( web.url_for( controller='admin', - action='library_dataset_dataset_association', - id=id, - library_id=library_id, - information=True, - msg=util.sanitize_text( msg ), - messagetype='done' ) ) + old_name = ldda.name + new_name = util.restore_text( params.get( 'name', '' ) ) + new_info = util.restore_text( params.get( 'info', '' ) ) + if not new_name: + msg = 'Enter a valid name' + messagetype = 'error' + else: + ldda.name = new_name + ldda.info = new_info + # The following for loop will save all metadata_spec items + for name, spec in ldda.datatype.metadata_spec.items(): + if spec.get("readonly"): + continue + optional = params.get( "is_" + name, None ) + if optional and optional == 'true': + # optional element... == 'true' actually means it is NOT checked (and therefore ommitted) + setattr( ldda.metadata, name, None ) + else: + setattr( ldda.metadata, name, spec.unwrap( params.get ( name, None ) ) ) + ldda.metadata.dbkey = dbkey + ldda.datatype.after_edit( ldda ) + trans.app.model.flush() + msg = 'Attributes updated for library dataset %s' % ldda.name + messagetype = 'done' + return trans.fill_template( "/admin/library/ldda_info.mako", + ldda=ldda, + library_id=library_id, + datatypes=ldatatypes, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) elif params.get( 'detect', False ): # The user clicked the Auto-detect button on the 'Edit Attributes' form for name, spec in ldda.datatype.metadata_spec.items(): @@ -1300,25 +1386,29 @@ class Admin( BaseController ): ldda.datatype.set_meta( ldda ) ldda.datatype.after_edit( ldda ) trans.app.model.flush() - msg = 'Attributes updated for dataset %s' % ldda.name - return trans.response.send_redirect( web.url_for( controller='admin', - action='library_dataset_dataset_association', - id=id, - library_id=library_id, - information=True, - msg=util.sanitize_text( msg ), - messagetype='done' ) ) + msg = 'Attributes updated for library dataset %s' % ldda.name + return trans.fill_template( "/admin/library/ldda_info.mako", + ldda=ldda, + library_id=library_id, + datatypes=ldatatypes, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) elif params.get( 'delete', False ): # TODO: need to revamp the way we remove datasets from disk. # The user selected the "Remove this dataset from the library" pop-up menu option ldda.deleted = True ldda.flush() msg = 'Dataset %s has been removed from this library' % ldda.name - trans.response.send_redirect( web.url_for( controller='admin', - action='browse_library', - id=library_id, - msg=util.sanitize_text( msg ), - messagetype='done' ) ) + return trans.fill_template( "/admin/library/ldda_info.mako", + ldda=ldda, + library_id=library_id, + datatypes=ldatatypes, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) ldda.datatype.before_edit( ldda ) if "dbkey" in ldda.datatype.metadata_spec and not ldda.metadata.dbkey: # Copy dbkey into metadata, for backwards compatability @@ -1327,14 +1417,12 @@ class Admin( BaseController ): # case it resorts to the old dbkey. Setting the dbkey # sets it properly in the metadata ldda.metadata.dbkey = ldda.dbkey - # let's not overwrite the imported datatypes module with the variable datatypes? - ### the built-in 'id' is overwritten in lots of places as well - ldatatypes = [x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys()] - ldatatypes.sort() return trans.fill_template( "/admin/library/ldda_info.mako", ldda=ldda, library_id=library_id, datatypes=ldatatypes, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), msg=msg, messagetype=messagetype ) elif ids: @@ -1359,33 +1447,55 @@ class Admin( BaseController ): messagetype='error' ) ) if action == 'permissions': if params.get( 'update_roles_button', False ): - #p = util.Params( kwd ) permissions = {} for k, v in trans.app.model.Dataset.permitted_actions.items(): in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ] permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles for ldda in lddas: + # Set the DATASET permissions on the Dataset trans.app.security_agent.set_all_dataset_permissions( ldda.dataset, permissions ) ldda.dataset.refresh() + permissions = {} + for k, v in trans.app.model.Library.permitted_actions.items(): + in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ] + permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles + for ldda in lddas: + # Set the LIBRARY permissions on the LibraryDataset + # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions + trans.app.security_agent.set_all_library_permissions( ldda.library_dataset, permissions ) + ldda.library_dataset.refresh() + # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation + trans.app.security_agent.set_all_library_permissions( ldda, permissions ) + ldda.refresh() msg = 'Permissions and roles have been updated on %d datasets' % len( lddas ) return trans.fill_template( "/admin/library/ldda_permissions.mako", ldda=lddas, library_id=library_id, msg=msg, messagetype=messagetype ) - # Ensure that the permissions across all datasets are identical. Otherwise, we can't update together. - tmp = [] + # Ensure that the permissions across all library items are identical, otherwise we can't update them together. + check_list = [] for ldda in lddas: - perms = trans.app.security_agent.get_dataset_permissions( ldda.dataset ) - if perms not in tmp: - tmp.append( perms ) - if len( tmp ) != 1: - msg = 'The datasets you selected do not have identical permissions, so they can not be updated together' - trans.response.send_redirect( web.url_for( controller='admin', - action='browse_library', - id=library_id, - msg=util.sanitize_text( msg ), - messagetype='error' ) ) + permissions = [] + # Check the library level permissions - the permissions on the LibraryDatasetDatasetAssociation + # will always be the same as the permissions on the associated LibraryDataset, so we only need to + # check one Library object + for library_permission in trans.app.security_agent.get_library_dataset_permissions( ldda.library_dataset ): + if library_permission.action not in permissions: + permissions.append( library_permission.action ) + for dataset_permission in trans.app.security_agent.get_dataset_permissions( ldda.dataset ): + if dataset_permission.action not in permissions: + permissions.append( dataset_permission.action ) + permissions.sort() + if not check_list: + check_list = permissions + if permissions != check_list: + msg = 'The datasets you selected do not have identical permissions, so they can not be updated together' + trans.response.send_redirect( web.url_for( controller='admin', + action='browse_library', + id=library_id, + msg=util.sanitize_text( msg ), + messagetype='error' ) ) return trans.fill_template( "/admin/library/ldda_permissions.mako", ldda=lddas, library_id=library_id, @@ -1393,7 +1503,7 @@ class Admin( BaseController ): messagetype=messagetype ) @web.expose @web.require_admin - def add_history_datasets_to_library( self, trans, ids="", library_id=None, folder_id=None, **kwd ): + def add_history_datasets_to_library( self, trans, library_id, folder_id, hda_ids='', **kwd ): params = util.Params( kwd ) msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) @@ -1406,6 +1516,10 @@ class Admin( BaseController ): id=library_id, msg=util.sanitize_text( msg ), messagetype='error' ) ) + try: + replace_dataset = trans.app.model.LibraryDataset.get( int( params.get( 'replace_id', None ) ) ) + except: + replace_dataset = None # See if the current history is empty history = trans.get_history() history.refresh() @@ -1416,238 +1530,339 @@ class Admin( BaseController ): id=library_id, msg=util.sanitize_text( msg ), messagetype='error' ) ) - if params.get( 'add_dataset_from_history_button', False ): - dataset_names = [] - ids = util.listify( ids ) - if ids: - for data_id in ids: - data = trans.app.model.HistoryDatasetAssociation.get( data_id ) - if data: - data.to_library_dataset_dataset_association( target_folder=folder ) - dataset_names.append( data.name ) + if params.get( 'add_history_datasets_to_library_button', False ): + hda_ids = util.listify( hda_ids ) + if hda_ids: + dataset_names = [] + created_ldda_ids = '' + for hda_id in hda_ids: + hda = trans.app.model.HistoryDatasetAssociation.get( hda_id ) + if hda: + ldda = hda.to_library_dataset_dataset_association( target_folder=folder ) + created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( ldda.id ) ) + dataset_names.append( ldda.name ) + if replace_dataset: + # If we are replacing versions and we receive a list, we add all the datasets + # and set the last one in the list as current + replace_dataset.set_library_dataset_dataset_association( ldda ) + # Copy the LDDA and LibrarDataset level permissions from replace_dataset to the new LDDA and LibraryDataset + trans.app.security_agent.copy_library_permissions( replace_dataset.library_dataset_dataset_association, ldda ) + trans.app.security_agent.copy_library_permissions( replace_dataset.library_dataset, ldda.library_dataset ) + else: + # If replace_dataset is None, the Library level permissions will be taken from the folder and applied to the new + # LDDA and LibraryDataset. + trans.app.security_agent.copy_library_permissions( folder, ldda ) + trans.app.security_agent.copy_library_permissions( folder, ldda.library_dataset ) else: - msg = "The requested dataset id %s is invalid" % str( data_id ) + msg = "The requested HistoryDatasetAssociation id %s is invalid" % str( hda_id ) return trans.response.send_redirect( web.url_for( controller='admin', action='browse_library', id=library_id, msg=util.sanitize_text( msg ), messagetype='error' ) ) - if dataset_names: - msg = "Added the following datasets to the library folder: %s" % ( ", ".join( dataset_names ) ) + if created_ldda_ids: + created_ldda_ids = created_ldda_ids.lstrip( ',' ) + ldda_id_list = created_ldda_ids.split( ',' ) + total_added = len( ldda_id_list ) + if replace_dataset: + msg = "Added %d dataset versions to the library dataset '%s' in the folder '%s'." % ( total_added, replace_dataset.name, folder.name ) + else: + msg = "Added %d datasets to the library folder '%s' ( each is selected ). " % ( total_added, folder.name ) + msg += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary." return trans.response.send_redirect( web.url_for( controller='admin', action='browse_library', id=library_id, + created_ldda_ids=created_ldda_ids, msg=util.sanitize_text( msg ), - messagetype='done' ) ) + messagetype='done' ) ) else: - msg = 'Select at least one dataset from the list' + msg = 'Select at least one dataset from the list of active datasets in your current history' messagetype = 'error' - return trans.fill_template( "/admin/library/new_dataset.mako", - history=history, - library_id=library_id, - folder=folder, - msg=msg, - messagetype=messagetype ) + last_used_build = folder.genome_build + upload_option = params.get( 'upload_option', 'upload_file' ) + # Send list of data formats to the form so the "extension" select list can be populated dynamically + file_formats = trans.app.datatypes_registry.upload_file_formats + # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically + def get_dbkey_options( last_used_build ): + for dbkey, build_name in util.dbnames: + yield build_name, dbkey, ( dbkey==last_used_build ) + dbkeys = get_dbkey_options( last_used_build ) + # Send list of roles to the form so the dataset can be associated with 1 or more of them. + roles = trans.app.model.Role.filter( trans.app.model.Role.table.c.deleted==False ).order_by( trans.app.model.Role.c.name ).all() + return trans.fill_template( "/admin/library/new_dataset.mako", + upload_option=upload_option, + library_id=library_id, + folder_id=folder_id, + file_formats=file_formats, + dbkeys=dbkeys, + last_used_build=last_used_build, + roles=roles, + history=history, + msg=msg, + messagetype=messagetype ) @web.expose @web.require_admin - def library_item_info_template( self, trans, id=None, new_element_count=0, library_id=None, - folder_id=None, library_dataset_id=None, ldda_id=None, **kwd ): + def info_template( self, trans, library_id, id=None, num_fields=0, folder_id=None, ldda_id=None, library_dataset_id=None, **kwd ): params = util.Params( kwd ) msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) - new_element_count = int( new_element_count ) - liit = None - if id: + if not num_fields: + num_fields = 0 + else: + num_fields = int( num_fields ) + if params.get( 'new_template', False ): + action = 'new_template' + else: + action = 'edit_template' + if folder_id: + library_item = trans.app.model.LibraryFolder.get( int( folder_id ) ) + library_item_desc = 'folder' + response_action = 'folder' + response_id = folder_id + elif ldda_id: + library_item = trans.app.model.LibraryDatasetDatasetAssociation.get( int( ldda_id ) ) + library_item_desc = 'library dataset <-> dataset association' + response_action = 'library_dataset_dataset_association' + response_id = ldda_id + elif library_dataset_id: + library_item = trans.app.model.LibraryDataset.get( int( library_dataset_id ) ) + library_item_desc = 'library dataset' + response_action = 'library_dataset_dataset_association' + response_id = library_item.library_dataset_dataset_association.id + else: + library_item = trans.app.model.Library.get( int( library_id ) ) + library_item_desc = 'library' + response_action = 'browse_library' + response_id = library_id + if action == 'new_template': + if params.get( 'create_info_template_button', False ): + return trans.fill_template( '/admin/library/new_info_template.mako', + library_item_name=library_item.name, + library_item_desc=library_item_desc, + num_fields=num_fields, + library_id=library_id, + folder_id=folder_id, + ldda_id=ldda_id, + library_dataset_id=library_dataset_id, + msg=msg, + messagetype=messagetype ) + elif params.get( 'new_info_template_button', False ): + # Make sure at least 1 template field is filled in + # TODO: Eventually we'll enhance templates to allow for required and optional fields. + proceed = False + for i in range( int( params.get( 'set_num_fields', 0 ) ) ): + elem_name = params.get( 'new_element_name_%i' % i, None ) + elem_description = params.get( 'new_element_description_%i' % i, None ) + if elem_name or elem_description: + proceed = True + break + if not proceed: + msg = "At least 1 of the fields must be completed." + return trans.fill_template( '/admin/library/new_info_template.mako', + library_item_name=library_item.name, + library_item_desc=library_item_desc, + num_fields=num_fields, + library_id=library_id, + folder_id=folder_id, + ldda_id=ldda_id, + library_dataset_id=library_dataset_id, + msg=msg, + messagetype=messagetype ) + # Create template then display edit screen + liit = trans.app.model.LibraryItemInfoTemplate() + liit.name = util.restore_text( params.get( 'name', '' ) ) + liit.description = util.restore_text( params.get( 'description', '' ) ) + liit.flush() + # Create template association + if folder_id: + liit_assoc = trans.app.model.LibraryFolderInfoTemplateAssociation() + liit_assoc.folder = trans.app.model.LibraryFolder.get( folder_id ) + elif ldda_id: + liit_assoc = trans.app.model.LibraryDatasetDatasetInfoTemplateAssociation() + ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id ) + liit_assoc.library_dataset_dataset_association = ldda + # This response_action method requires a folder_id + folder_id = ldda.library_dataset.folder.id + elif library_dataset_id: + liit_assoc = trans.app.model.LibraryDatasetInfoTemplateAssociation() + library_dataset = trans.app.model.LibraryDataset.get( library_dataset_id ) + liit_assoc.library_dataset = library_dataset + # This response_action method requires a folder_id + folder_id = library_dataset.folder.id + else: + # We'll always be sent a library_id + liit_assoc = trans.app.model.LibraryInfoTemplateAssociation() + liit_assoc.library = trans.app.model.Library.get( library_id ) + liit_assoc.library_item_info_template = liit + liit_assoc.flush() + # Create and add elements + for i in range( int( params.get( 'set_num_fields', 0 ) ) ): + elem_name = params.get( 'new_element_name_%i' % i, None ) + elem_description = params.get( 'new_element_description_%i' % i, None ) + if elem_description and not elem_name: + # If we have a description but no name, the description will be both + # ( a name cannot be empty, but a description can ) + elem_name = elem_description + if elem_name: + # Skip any elements that have a missing name + liit.add_element( name=elem_name, description=elem_description ) + msg = "The new information template has been created." + return trans.response.send_redirect( web.url_for( controller='admin', + action=response_action, + id=response_id, + library_id=library_id, + folder_id=folder_id, + msg=util.sanitize_text( msg ), + messagetype='done' ) ) + return trans.fill_template( '/admin/library/create_info_template.mako', + library_item=library_item, + library_id=library_id, + msg=msg, + messagetype=messagetype ) + elif action == 'edit_template': try: - liit = trans.app.model.LibraryItemInfoTemplate.get( id ) + liit = trans.app.model.LibraryItemInfoTemplate.get( int( id ) ) except: msg = "Invalid library info template specified, id: %s" % str( id ) - # TODO: this should probably redirect to the dataset method... return trans.response.send_redirect( web.url_for( controller='admin', - action='browse_library', - id=library_id, + action=response_action, + id=response_id, + library_id=library_id, msg=util.sanitize_text( msg ), messagetype='error' ) ) - if params.get( 'liit_create_button', False ): - # Create template then display edit screen - liit = trans.app.model.LibraryItemInfoTemplate() - liit.name = params.get( 'name', 'unnamed' ) - liit.description = params.get( 'description', '' ) - liit.flush() - # Create template association - if library_id: - liit_assoc = trans.app.model.LibraryInfoTemplateAssociation() - liit_assoc.library = trans.app.model.Library.get( library_id ) - elif folder_id: - liit_assoc = trans.app.model.LibraryFolderInfoTemplateAssociation() - liit_assoc.folder = trans.app.model.LibraryFolder.get( folder_id ) - elif library_dataset_id: - liit_assoc = trans.app.model.LibraryDatasetInfoTemplateAssociation() - liit_assoc.library_dataset = trans.app.model.LibraryDataset.get( library_dataset_id ) - elif ldda_id: - liit_assoc = trans.app.model.LibraryDatasetDatasetInfoTemplateAssociation() - liit_assoc.library_dataset_dataset_association = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id ) - liit_assoc.library_item_info_template = liit - liit_assoc.flush() - # Create and add elements - for i in range( int( params.get( 'set_element_count', 0 ) ) ): - elem_name = params.get( 'new_element_name_%i' % i, None ) - elem_description = params.get( 'new_element_description_%i' % i, None ) - # Skip any elements that have a missing name and description - if not elem_name: - # If we have a description but no name, the description will be both - # ( a name cannot be empty, but a description can ) - elem_name = elem_description - else: - liit.add_element( name=elem_name, description=elem_description ) - elif params.get( 'liit_edit_button', False ): - # Save changes to existing attributes, only set name if nonempty/nonNone is passed, but always set description - name = params.get( 'name', None ) - if name: - liit.name = name - liit.description = params.get( 'description', '' ) - liit.flush() - # Save changes to exisiting elements - for elem_id in params.get( 'element_ids', [] ): - liit_element = trans.app.model.LibraryItemInfoTemplateElement.get( elem_id ) - name = params.get( 'element_name_%s' % elem_id, None ) + if params.get( 'edit_info_template_button', False ): + # Save changes to existing attributes, only set name if nonempty/nonNone is passed, but always set description + name = params.get( 'name', None ) if name: - liit_element.name = name - liit_element.description = params.get( 'element_description_%s' % elem_id, None ) - liit_element.flush() - # Add new elements - for i in range( int( params.get( 'set_element_count', 0 ) ) ): - elem_name = params.get( 'new_element_name_%i' % i, None ) - elem_description = params.get( 'new_element_description_%i' % i, None ) - # Skip any elements that have a missing name and description - if not elem_name: - # If we have a description but no name, the description will be both - # ( a name cannot be empty, but a description can ) - elem_name = elem_description - if elem_name: - liit.add_element( name=elem_name, description=elem_description ) - liit.refresh() - msg = 'Information template %s has been updated' % liit.name - if library_id: - library_item_name = trans.app.model.Library.get( library_id ).name - library_item_desc = 'library' - elif folder_id: - library_item_name = trans.app.model.LibraryFolder.get( folder_id ).name - library_item_desc = 'folder' - elif library_dataset_id: - ld = trans.app.model.LibraryDataset.get( library_dataset_id ) - library_item_name = ld.name - library_item_desc = 'library dataset' - elif ldda_id: - library_item_name = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id ).name - library_item_desc = 'library dataset <-> dataset association' - else: - library_item_name = 'unknown' - library_item_desc = '' - return trans.fill_template( "/admin/library/item_info_template.mako", - liit=liit, - new_element_count=new_element_count, - library_id=library_id, - library_dataset_id=library_dataset_id, - ldda_id=ldda_id, - folder_id=folder_id, - library_item_name=library_item_name, - library_item_desc=library_item_desc, - msg=msg, - messagetype=messagetype ) + liit.name = name + liit.description = params.get( 'description', '' ) + liit.flush() + # Save changes to exisiting elements + for elem_id in params.get( 'element_ids', [] ): + liit_element = trans.app.model.LibraryItemInfoTemplateElement.get( elem_id ) + name = params.get( 'element_name_%s' % elem_id, None ) + if name: + liit_element.name = name + liit_element.description = params.get( 'element_description_%s' % elem_id, None ) + liit_element.flush() + # Add new elements + for i in range( int( params.get( 'set_num_fields', 0 ) ) ): + elem_name = params.get( 'new_element_name_%i' % i, None ) + elem_description = params.get( 'new_element_description_%i' % i, None ) + # Skip any elements that have a missing name and description + if not elem_name: + # If we have a description but no name, the description will be both + # ( a name cannot be empty, but a description can ) + elem_name = elem_description + if elem_name: + liit.add_element( name=elem_name, description=elem_description ) + liit.refresh() + msg = 'Information template %s has been updated' % liit.name + return trans.fill_template( "/admin/library/edit_info_template.mako", + liit=liit, + num_fields=num_fields, + library_id=library_id, + library_dataset_id=library_dataset_id, + ldda_id=ldda_id, + folder_id=folder_id, + library_item_name=library_item.name, + library_item_desc=library_item_desc, + msg=msg, + messagetype=messagetype ) @web.expose @web.require_admin - def library_item_info( self, trans, do_action='display', id=None, library_item_id=None, library_item_type=None, library_id=None, **kwd ): + def library_item_info( self, trans, library_id, id=None, library_item_id=None, library_item_type=None, **kwd ): params = util.Params( kwd ) msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) + folder_id = None if id: item_info = trans.app.model.LibraryItemInfo.get( id ) else: item_info = None if library_item_type == 'library': library_item = trans.app.model.Library.get( library_item_id ) - error_action = 'browse' elif library_item_type == 'library_dataset': library_item = trans.app.model.LibraryDataset.get( library_item_id ) - error_action = 'library_dataset' elif library_item_type == 'folder': library_item = trans.app.model.LibraryFolder.get( library_item_id ) elif library_item_type == 'library_dataset_dataset_association': - error_action = 'folder' library_item = trans.app.model.LibraryDatasetDatasetAssociation.get( library_item_id ) - error_action = 'library_dataset_dataset_association' + # This response_action method requires a folder_id + folder_id = library_item.library_dataset.folder.id else: library_item_type == None library_item = None - if not item_info and not library_item_type: - msg = "Unable to perform requested action (%s)." % do_action + if not library_item: + msg = "Invalid library item type ( %s ) specified, id ( %s )" % ( str( library_item_type ), str( library_item_id ) ) return trans.response.send_redirect( web.url_for( controller='admin', - action=error_action, - id=library_item_id, + action='browse_library', + id=library_id, msg=util.sanitize_text( msg ), messagetype='error' ) ) - #TODO: not sure we need the display option or the display_info.mako template any longer... - if do_action == 'display': - return trans.fill_template( "/admin/library/display_info.mako", - item_info=item_info, - msg=msg, - messagetype=messagetype ) - elif do_action == 'new_info': + if params.get( 'new_info', False ): if library_item: - if trans.app.security_agent.allow_action( trans.user, - trans.app.security_agent.permitted_actions.LIBRARY_ADD, - library_item=library_item ): - if params.get( 'create_new_info_button', False ): - user = trans.get_user() - #create new info then send back to make more - library_item_info_template_id = params.get( 'library_item_info_template_id', None ) - library_item_info_template = trans.app.model.LibraryItemInfoTemplate.get( library_item_info_template_id ) - library_item_info = trans.app.model.LibraryItemInfo() - library_item_info.library_item_info_template = library_item_info_template - library_item_info.user = user - library_item_info.flush() - trans.app.security_agent.copy_library_permissions( library_item_info_template, library_item_info, user=user ) - for template_element in library_item_info_template.elements: - info_element_value = params.get( "info_element_%s_%s" % ( library_item_info_template.id, template_element.id), None ) - info_element = trans.app.model.LibraryItemInfoElement() - info_element.contents = info_element_value - info_element.library_item_info_template_element = template_element - info_element.library_item_info = library_item_info - info_element.flush() - info_association_class = None - for item_class, permission_class, info_association_class in trans.app.security_agent.library_item_assocs: - if isinstance( library_item, item_class ): - break - if info_association_class: - library_item_info_association = info_association_class() - library_item_info_association.set_library_item( library_item ) - library_item_info_association.library_item_info = library_item_info - library_item_info_association.user = user - library_item_info_association.flush() - else: - raise 'Invalid class (%s) specified for library_item (%s)' % ( library_item.__class__, library_item.__class__.__name__ ) - # TODO: make sure we don't need to set permissions on the association object. - msg = 'The information has been saved. You can add more information if necessary.' + if params.get( 'create_new_info_button', False ): + library_item_info_template_id = params.get( 'library_item_info_template_id', None ) + library_item_info_template = trans.app.model.LibraryItemInfoTemplate.get( int( library_item_info_template_id ) ) + # Make sure at least 1 template field is filled in + # TODO: Eventually we'll enhance templates to allow for required and optional fields. + proceed = False + for template_element in library_item_info_template.elements: + if params.get( "info_element_%s_%s" % ( library_item_info_template.id, template_element.id ), None ): + proceed = True + break + if not proceed: + msg = "At least 1 of the fields must be completed." return trans.response.send_redirect( web.url_for( controller='admin', action=library_item_type, id=library_item.id, library_id=library_id, + folder_id=folder_id, + information=True, msg=util.sanitize_text( msg ), - messagetype='done' ) ) - return trans.fill_template( "/admin/library/new_info.mako", - library_item=library_item, - library_item_type=library_item_type, - library_id=library_id, - msg=msg, - messagetype=messagetype ) - else: - return trans.show_error_message( "You do not have permission to add information to this library item." ) + messagetype='error' ) ) + user = trans.get_user() + library_item_info = trans.app.model.LibraryItemInfo() + library_item_info.library_item_info_template = library_item_info_template + library_item_info.user = user + library_item_info.flush() + trans.app.security_agent.copy_library_permissions( library_item_info_template, library_item_info ) + for template_element in library_item_info_template.elements: + info_element_value = params.get( "info_element_%s_%s" % ( library_item_info_template.id, template_element.id ), None ) + info_element = trans.app.model.LibraryItemInfoElement() + info_element.contents = info_element_value + info_element.library_item_info_template_element = template_element + info_element.library_item_info = library_item_info + info_element.flush() + info_association_class = None + for item_class, permission_class, info_association_class in trans.app.security_agent.library_item_assocs: + if isinstance( library_item, item_class ): + break + if info_association_class: + library_item_info_association = info_association_class() + library_item_info_association.set_library_item( library_item, trans.user ) + library_item_info_association.library_item_info = library_item_info + library_item_info_association.user = user + library_item_info_association.flush() + else: + raise 'Invalid class (%s) specified for library_item (%s)' % ( library_item.__class__, library_item.__class__.__name__ ) + msg = 'The information has been saved' + return trans.response.send_redirect( web.url_for( controller='admin', + action=library_item_type, + id=library_item.id, + library_id=library_id, + folder_id=folder_id, + information=True, + msg=util.sanitize_text( msg ), + messagetype='done' ) ) + return trans.fill_template( "/admin/library/new_info.mako", + library_id=library_id, + library_item=library_item, + library_item_type=library_item_type, + msg=msg, + messagetype=messagetype ) # TODO: add more functionality -> user's should be able to edit/delete, etc, and create/delete and edit templates - return trans.fill_template( "/admin/library/display_info.mako", - item_info=item_info, - msg="Unable to perform requested action (%s)." % do_action, - messagetype=messagetype ) @web.expose @web.require_admin def download_dataset_from_folder(self, trans, id, library_id=None, **kwd): @@ -1680,7 +1895,7 @@ class Admin( BaseController ): messagetype='error' ) ) @web.expose @web.require_admin - def datasets( self, trans, library_id=None, **kwd ): + def datasets( self, trans, library_id, **kwd ): # This method is used by the select list labeled "Perform action on selected datasets" # on the admin library browser. params = util.Params( kwd ) @@ -1696,10 +1911,13 @@ class Admin( BaseController ): messagetype='error' ) ) ldda_ids = util.listify( params.ldda_ids ) if params.action == 'edit': + # We need the folder containing the LibraryDatasetDatasetAssociation(s) + ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_ids[0] ) trans.response.send_redirect( web.url_for( controller='admin', action='library_dataset_dataset_association', - id=",".join( ldda_ids ), library_id=library_id, + folder_id=ldda.library_dataset.folder.id, + id=",".join( ldda_ids ), permissions=True, msg=util.sanitize_text( msg ), messagetype=messagetype ) ) diff --git a/lib/galaxy/web/controllers/dataset.py b/lib/galaxy/web/controllers/dataset.py index acb0866d76a..de800dea7c1 100644 --- a/lib/galaxy/web/controllers/dataset.py +++ b/lib/galaxy/web/controllers/dataset.py @@ -1,15 +1,9 @@ +import logging, os, mimetypes, smtplib + from galaxy.web.base.controller import * - -import logging, os, sets, string, shutil, tempfile, StringIO, urllib -import re, socket -import mimetypes - -from galaxy import util, datatypes, jobs, web, util, model -from galaxy.datatypes import sniff -from galaxy.security import RBACAgent +from galaxy import web, model from cgi import escape, FieldStorage -import smtplib from email.MIMEText import MIMEText import pkg_resources; @@ -226,210 +220,3 @@ class DatasetInterface( BaseController ): new_history_name = new_history_name, done_msg = done_msg, error_msg = error_msg ) - -def add_file( trans, file_obj, name, extension, dbkey, roles, info='no info', space_to_tab=False, - replace_dataset=None, permission_source=None, folder_id=None ): - def check_gzip( temp_name ): - # Utility method to check gzipped uploads - temp = open( temp_name, "U" ) - magic_check = temp.read( 2 ) - temp.close() - if magic_check != util.gzip_magic: - return ( False, False ) - CHUNK_SIZE = 2**15 # 32Kb - gzipped_file = gzip.GzipFile( temp_name ) - chunk = gzipped_file.read( CHUNK_SIZE ) - gzipped_file.close() - return ( True, True ) - data_type = None - temp_name = sniff.stream_to_file( file_obj ) - # See if we have a gzipped file, which, if it passes our restrictions, we'll uncompress on the fly. - is_gzipped, is_valid = check_gzip( temp_name ) - if is_gzipped and not is_valid: - raise BadFileException( "you attempted to upload an inappropriate file." ) - elif is_gzipped and is_valid: - # We need to uncompress the temp_name file - CHUNK_SIZE = 2**20 # 1Mb - fd, uncompressed = tempfile.mkstemp() - gzipped_file = gzip.GzipFile( temp_name ) - while 1: - try: - chunk = gzipped_file.read( CHUNK_SIZE ) - except IOError: - os.close( fd ) - os.remove( uncompressed ) - raise BadFileException( 'problem uncompressing gzipped data.' ) - if not chunk: - break - os.write( fd, chunk ) - os.close( fd ) - gzipped_file.close() - # Replace the gzipped file with the decompressed file - shutil.move( uncompressed, temp_name ) - name = name.rstrip( '.gz' ) - data_type = 'gzip' - if space_to_tab: - line_count = sniff.convert_newlines_sep2tabs( temp_name ) - elif os.stat( temp_name ).st_size < 262144000: # 250MB - line_count = sniff.convert_newlines( temp_name ) - else: - if sniff.check_newlines( temp_name ): - line_count = sniff.convert_newlines( temp_name ) - else: - line_count = None - if extension == 'auto': - data_type = sniff.guess_ext( temp_name, sniff_order=trans.app.datatypes_registry.sniff_order ) - else: - data_type = extension - if replace_dataset: - library_dataset = replace_dataset - else: - library_dataset = trans.app.model.LibraryDataset( name=name, info=info, extension=data_type, dbkey=dbkey ) - library_dataset.flush() - if permission_source: - trans.app.security_agent.copy_library_permissions( permission_source, library_dataset, user=trans.get_user() ) - ldda = trans.app.model.LibraryDatasetDatasetAssociation( name=name, - info=info, - extension=data_type, - dbkey=dbkey, - library_dataset = library_dataset, - create_dataset=True ) - ldda.flush() - if permission_source: - trans.app.security_agent.copy_library_permissions( permission_source, ldda, user=trans.get_user() ) - if not replace_dataset: - folder = trans.app.model.LibraryFolder.get( folder_id ) - folder.add_dataset( library_dataset, genome_build=dbkey ) - library_dataset.library_dataset_dataset_association_id = ldda.id - library_dataset.flush() - if roles: - for role in roles: - dp = trans.app.model.DatasetPermissions( RBACAgent.permitted_actions.DATASET_ACCESS.action, ldda.dataset, role ) - dp.flush() - shutil.move( temp_name, ldda.dataset.file_name ) - ldda.dataset.state = ldda.dataset.states.OK - ldda.init_meta() - if line_count is not None: - try: - ldda.set_peek( line_count=line_count ) - except: - ldda.set_peek() - else: - ldda.set_peek() - ldda.set_size() - if ldda.missing_meta(): - ldda.datatype.set_meta( ldda ) - trans.app.model.flush() - return ldda - -def upload_dataset( trans, controller=None, folder_id=None, replace_dataset=None, replace_id=None, permission_source=None, **kwd ): - # This method is called from both the admin and library controllers - params = util.Params( kwd ) - msg = util.restore_text( params.get( 'msg', '' ) ) - messagetype = params.get( 'messagetype', 'done' ) - dbkey = params.get( 'dbkey', '?' ) - extension = params.get( 'extension', 'auto' ) - data_file = params.get( 'file_data', '' ) - url_paste = params.get( 'url_paste', '' ) - server_dir = params.get( 'server_dir', 'None' ) - if data_file == '' and url_paste == '' and server_dir in [ 'None', '' ]: - if trans.app.config.library_import_dir is not None: - msg = 'Select a file, enter a URL or Text, or select a server directory.' - else: - msg = 'Select a file, enter a URL or enter Text.' - trans.response.send_redirect( web.url_for( controller=controller, - action='library_dataset_dataset_association', - folder_id=folder_id, - replace_id=replace_id, - msg=util.sanitize_text( msg ), - messagetype='done' ) ) - space_to_tab = params.get( 'space_to_tab', False ) - if space_to_tab and space_to_tab not in [ "None", None ]: - space_to_tab = True - roles = [] - for role_id in util.listify( params.get( 'roles', [] ) ): - roles.append( trans.app.model.Role.get( role_id ) ) - temp_name = "" - data_list = [] - created_ldda_ids = '' - if 'filename' in dir( data_file ): - file_name = data_file.filename - file_name = file_name.split( '\\' )[-1] - file_name = file_name.split( '/' )[-1] - created_ldda = add_file( trans, - data_file.file, - file_name, - extension, - dbkey, - roles, - info="uploaded file", - space_to_tab=space_to_tab, - replace_dataset=replace_dataset, - permission_source=permission_source, - folder_id=folder_id ) - created_ldda_ids = str( created_ldda.id ) - elif url_paste not in [ None, "" ]: - if url_paste.lower().find( 'http://' ) >= 0 or url_paste.lower().find( 'ftp://' ) >= 0: - url_paste = url_paste.replace( '\r', '' ).split( '\n' ) - for line in url_paste: - line = line.rstrip( '\r\n' ) - if line: - created_ldda = add_file( trans, - urllib.urlopen( line ), - line, - extension, - dbkey, - roles, - info="uploaded url", - space_to_tab=space_to_tab, - replace_dataset=replace_dataset, - permission_source=permission_source, - folder_id=folder_id ) - created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( created_ldda.id ) ) - else: - is_valid = False - for line in url_paste: - line = line.rstrip( '\r\n' ) - if line: - is_valid = True - break - if is_valid: - created_ldda = add_file( trans, - StringIO.StringIO( url_paste ), - 'Pasted Entry', - extension, - dbkey, - roles, - info="pasted entry", - space_to_tab=space_to_tab, - replace_dataset=replace_dataset, - permission_source=permission_source, - folder_id=folder_id ) - created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( created_ldda.id ) ) - elif server_dir not in [ None, "", "None" ]: - full_dir = os.path.join( trans.app.config.library_import_dir, server_dir ) - try: - files = os.listdir( full_dir ) - except: - log.debug( "Unable to get file list for %s" % full_dir ) - for file in files: - full_file = os.path.join( full_dir, file ) - if not os.path.isfile( full_file ): - continue - created_ldda = add_file( trans, - open( full_file, 'rb' ), - file, - extension, - dbkey, - roles, - info="imported file", - space_to_tab=space_to_tab, - replace_dataset=replace_dataset, - permission_source=permission_source, - folder_id=folder_id ) - created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( created_ldda.id ) ) - if created_ldda_ids: - created_ldda_ids = created_ldda_ids.lstrip( ',' ) - return created_ldda_ids - else: - return '' diff --git a/lib/galaxy/web/controllers/history.py b/lib/galaxy/web/controllers/history.py index 5224fab844f..5868ec7efc6 100644 --- a/lib/galaxy/web/controllers/history.py +++ b/lib/galaxy/web/controllers/history.py @@ -1,4 +1,7 @@ from galaxy.web.base.controller import * +from galaxy.web.framework.helpers.grids import * +import webhelpers +from datetime import datetime from cgi import escape log = logging.getLogger( __name__ ) @@ -6,83 +9,98 @@ log = logging.getLogger( __name__ ) # States for passing messages SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error" -class HistoryController( BaseController ): +def time_ago( x ): + return webhelpers.date.distance_of_time_in_words( x, datetime.utcnow() ) + +def iff( a, b, c ): + if a: + return b + else: + return c + +class HistoryListGrid( Grid ): - @web.expose - def index( self, trans ): - return "" + title = "Stored histories" + model_class = model.History + default_sort_key = "-create_time" + columns = [ + GridColumn( "Name", key="name", + link=( lambda item: iff( item.deleted, None, dict( operation="switch", id=item.id ) ) ), + attach_popup=True ), + GridColumn( "Datasets (by state)", method='_build_datasets_by_state', ncells=4 ), + GridColumn( "Status", method='_build_status' ), + GridColumn( "Age", key="create_time", format=time_ago ), + GridColumn( "Last update", key="update_time", format=time_ago ), + # Valid for filtering but invisible + GridColumn( "Deleted", key="deleted", visible=False ) + ] + operations = [ + GridOperation( "Switch", allow_multiple=False, condition=( lambda item: not item.deleted ) ), + GridOperation( "Share", condition=( lambda item: not item.deleted ) ), + GridOperation( "Rename", condition=( lambda item: not item.deleted ) ), + GridOperation( "Delete", condition=( lambda item: not item.deleted ) ), + GridOperation( "Undelete", condition=( lambda item: item.deleted ) ) + ] + standard_filters = [ + GridColumnFilter( "Active", args=dict( deleted=False ) ), + GridColumnFilter( "Deleted", args=dict( deleted=True ) ), + GridColumnFilter( "All", args=dict( deleted='All' ) ) + ] + default_filter = dict( deleted=False ) - @web.expose - def list_as_xml( self, trans ): - """ - XML history list for functional tests - """ - return trans.fill_template( "/history/list_as_xml.mako" ) + def get_current_item( self, trans ): + return trans.history - @web.expose - @web.require_login( "work with multiple histories" ) - def list( self, trans, id=[], operation=None, show_deleted = False, **kwd ): - """ - List all available histories - """ - # TODO: these two operations need to be updates still - if operation: - operation = operation.lower() - if operation == "share": - return self.share( trans, id, **kwd ) - elif operation == "rename": - return self.rename( trans, id, **kwd ) - # Coerce ids to list - if not isinstance( id, list ): - id = id.split( "," ) - # Ensure ids are integers - try: - history_ids = map( int, id ) - except: - return trans.show_error_message( "Invalid history id" ) - # Note that this page was loaded - trans.log_event( "History id %s available" % str( id ) ) + def apply_default_filter( self, trans, query ): + return query.filter_by( user=trans.user, purged=False ) + + def handle_operation( self, trans, operation, history_ids ): # Display no message by default status, message = None, None refresh_history = False - # If an operation was provided, load the histories and ensure they all - # belong to the current user - if history_ids and operation: - histories = [] - for hid in history_ids: - history = self.app.model.History.get( hid ) - if history: - # Ensure history is owned by current user - if history.user_id != None and trans.user: - assert trans.user.id == history.user_id, "History does not belong to current user" - histories.append( history ) - else: - log.warn( "Invalid history id '%r' passed to list", hid ) - operation = operation.lower() - if operation == "switch": - status, message = self._list_switch( trans, histories ) - refresh_history = True - elif operation == "share": - ## Caught above for now - pass - elif operation == "rename": - ## Caught above for now - pass - elif operation == "delete": - status, message = self._list_delete( trans, histories ) - elif operation == "undelete": - status, message = self._list_undelete( trans, histories ) - trans.app.model.flush() + # Load the histories and ensure they all belong to the current user + histories = [] + for hid in history_ids: + history = model.History.get( hid ) + if history: + # Ensure history is owned by current user + if history.user_id != None and trans.user: + assert trans.user.id == history.user_id, "History does not belong to current user" + histories.append( history ) + else: + log.warn( "Invalid history id '%r' passed to list", hid ) + operation = operation.lower() + if operation == "switch": + status, message = self._list_switch( trans, histories ) + refresh_history = True + elif operation == "share": + ## Caught above for now + pass + elif operation == "rename": + ## Caught above for now + pass + elif operation == "delete": + status, message = self._list_delete( trans, histories ) + elif operation == "undelete": + status, message = self._list_undelete( trans, histories ) + trans.sa_session.flush() # Render the list view - return trans.fill_template( "/history/list.mako", - ids = id, - user = trans.user, - current_history = trans.history, - show_deleted = util.string_as_bool( show_deleted ), - refresh_history = refresh_history, - message_type = status, - message = message - ) + return status, message + + def _build_datasets_by_state( self, trans, history ): + rval = [] + for state in ( 'ok', 'running', 'queued', 'error' ): + total = sum( 1 for d in history.active_datasets if d.state == state ) + if total: + rval.append( '
%s
' % ( state, total ) ) + else: + rval.append( '' ) + return rval + + def _build_status( self, trans, history ): + if history.deleted: + return "deleted" + return "" def _list_delete( self, trans, histories ): """Delete histories""" @@ -145,6 +163,51 @@ class HistoryController( BaseController ): trans.log_event( "History switched to id: %s, name: '%s'" % (str(new_history.id), new_history.name ) ) # No message return None, None + +class HistoryController( BaseController ): + + @web.expose + def index( self, trans ): + return "" + + @web.expose + def list_as_xml( self, trans ): + """ + XML history list for functional tests + """ + return trans.fill_template( "/history/list_as_xml.mako" ) + + _list_grid = HistoryListGrid() + + @web.expose + @web.require_login( "work with multiple histories" ) + def list( self, trans, *args, **kwargs ): + """ + List all available histories + """ + # TODO: these two operations need to be updates still + operation = kwargs.get( 'operation', None ) + if operation: + operation = operation.lower() + if operation == "share": + return self.share( trans, **kwargs ) + elif operation == "rename": + return self.rename( trans, **kwargs ) + return self._list_grid( trans, *args, **kwargs ) + + @web.expose + def rename_async( self, trans, id=None, new_name=None ): + history = model.History.get( id ) + # Check that the history exists, and is either owned by the current + # user (if logged in) or the current history + assert history is not None + if history.user is None: + assert history == trans.history + else: + assert history.user == trans.user + # Rename + history.name = new_name + trans.sa_session.flush() ## These have been moved from 'root' but not cleaned up diff --git a/lib/galaxy/web/controllers/library.py b/lib/galaxy/web/controllers/library.py index a5a0cba4251..f401c9c46de 100644 --- a/lib/galaxy/web/controllers/library.py +++ b/lib/galaxy/web/controllers/library.py @@ -1,5 +1,4 @@ from galaxy.web.base.controller import * -from galaxy.web.controllers.dataset import upload_dataset from galaxy.model.orm import * from galaxy.datatypes import sniff from galaxy import util @@ -14,16 +13,31 @@ log = logging.getLogger( __name__ ) class Library( BaseController ): @web.expose - def index( self, trans ): - return trans.fill_template( "/library/index.mako" ) + def index( self, trans, **kwd ): + params = util.Params( kwd ) + msg = util.restore_text( params.get( 'msg', '' ) ) + messagetype = params.get( 'messagetype', 'done' ) + return trans.fill_template( "/library/index.mako", + default_action=params.get( 'default_action', None ), + msg=msg, + messagetype=messagetype ) @web.expose def browse_libraries( self, trans, **kwd ): params = util.Params( kwd ) msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) + all_libraries = trans.app.model.Library.filter( trans.app.model.Library.table.c.deleted==False ).order_by( trans.app.model.Library.name ).all() + authorized_libraries = [] + for library in all_libraries: + if trans.app.security_agent.allow_action( trans.user, trans.app.security_agent.permitted_actions.LIBRARY_ADD, library_item=library ) or \ + trans.app.security_agent.allow_action( trans.user, trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, library_item=library ) or \ + trans.app.security_agent.allow_action( trans.user, trans.app.security_agent.permitted_actions.LIBRARY_MANAGE, library_item=library ) or \ + trans.app.security_agent.check_folder_contents( trans.user, library ) or \ + trans.app.security_agent.show_library_item( trans.user, library ): + authorized_libraries.append( library ) return trans.fill_template( '/library/browse_libraries.mako', - libraries=trans.app.model.Library.filter( trans.app.model.Library.table.c.deleted==False ) \ - .order_by( trans.app.model.Library.name ).all(), + libraries=authorized_libraries, + default_action=params.get( 'default_action', None ), msg=msg, messagetype=messagetype ) @web.expose @@ -36,6 +50,7 @@ class Library( BaseController ): msg = "You must specify a library id." return trans.response.send_redirect( web.url_for( controller='library', action='browse_libraries', + default_action=params.get( 'default_action', None ), msg=util.sanitize_text( msg ), messagetype='error' ) ) library = library=trans.app.model.Library.get( id ) @@ -43,6 +58,7 @@ class Library( BaseController ): msg = "Invalid library id ( %s )." return trans.response.send_redirect( web.url_for( controller='library', action='browse_libraries', + default_action=params.get( 'default_action', None ), msg=util.sanitize_text( msg ), messagetype='error' ) ) created_ldda_ids = params.get( 'created_ldda_ids', '' ) @@ -53,7 +69,79 @@ class Library( BaseController ): msg=msg, messagetype=messagetype ) @web.expose - def datasets( self, trans, ldda_ids=[], library_id=None, **kwd ): + def library( self, trans, id=None, **kwd ): + params = util.Params( kwd ) + msg = util.restore_text( params.get( 'msg', '' ) ) + messagetype = params.get( 'messagetype', 'done' ) + if not id: + msg = "You must specify a library." + return trans.response.send_redirect( web.url_for( controller='library', + action='browse_libraries', + msg=util.sanitize_text( msg ), + messagetype='error' ) ) + library = trans.app.model.Library.get( int( id ) ) + if not library: + msg = "Invalid library id ( %s ) specified." % str( id ) + return trans.response.send_redirect( web.url_for( controller='library', + action='browse_libraries', + msg=util.sanitize_text( msg ), + messagetype='error' ) ) + if params.get( 'delete', False ): + action = 'delete' + elif params.get( 'permissions', False ): + action = 'permissions' + else: + # 'information' is the default + action = 'information' + if action == 'information': + if params.get( 'rename_library_button', False ): + old_name = library.name + new_name = util.restore_text( params.name ) + new_description = util.restore_text( params.description ) + if not new_name: + msg = 'Enter a valid name' + return trans.fill_template( '/library/library_info.mako', + library=library, + restrict=params.get( 'restrict', False ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype='error' ) + else: + library.name = new_name + library.description = new_description + library.flush() + msg = "Library '%s' has been renamed to '%s'" % ( old_name, new_name ) + return trans.response.send_redirect( web.url_for( controller='library', + action='library', + id=id, + information=True, + msg=util.sanitize_text( msg ), + messagetype='done' ) ) + return trans.fill_template( '/library/library_info.mako', + library=library, + restrict=params.get( 'restrict', False ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) + elif action == 'permissions': + if params.get( 'update_roles_button', False ): + # The user clicked the Save button on the 'Associate With Roles' form + permissions = {} + for k, v in trans.app.model.Library.permitted_actions.items(): + in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ] + permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles + trans.app.security_agent.set_all_library_permissions( library, permissions ) + library.refresh() + msg = "Permissions updated for library '%s'" % library.name + return trans.response.send_redirect( web.url_for( controller='library', + action='library', + id=id, + permissions=True, + msg=util.sanitize_text( msg ), + messagetype='done' ) ) + return trans.fill_template( '/library/manage_library_permissions.mako', library=library, msg=msg, messagetype=messagetype ) + @web.expose + def datasets( self, trans, library_id, ldda_ids='', **kwd ): # This method is used by the select list labeled "Perform action on selected datasets" # on the analysis library browser. if not ldda_ids: @@ -76,19 +164,29 @@ class Library( BaseController ): messagetype='error' ) ) if params.do_action == 'add': history = trans.get_history() - for id in ldda_ids: - dataset = trans.app.model.LibraryDatasetDatasetAssociation.get( id ).to_history_dataset_association() - history.add_dataset( dataset ) - dataset.flush() + for ldda_id in ldda_ids: + ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id ) + hda = ldda.to_history_dataset_association( target_history=history ) + history.add_dataset( hda ) + hda.flush() history.flush() - return trans.show_ok_message( "%i dataset(s) have been imported into your history" % len( ldda_ids ), refresh_frames=['history'] ) + msg = "%i dataset(s) have been imported into your history" % len( ldda_ids ) + return trans.response.send_redirect( web.url_for( controller='library', + action='browse_library', + id=library_id, + msg=util.sanitize_text( msg ), + messagetype='done' ) ) elif params.do_action == 'manage_permissions': - # Do we need a security check here? + # We need the folder containing the LibraryDatasetDatasetAssociation(s) + ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_ids[0] ) trans.response.send_redirect( web.url_for( controller='library', action='library_dataset_dataset_association', - id=','.join( ldda_ids ), library_id=library_id, - **kwd ) ) + folder_id=ldda.library_dataset.folder.id, + id=','.join( ldda_ids ), + permissions=True, + msg=util.sanitize_text( msg ), + messagetype=messagetype ) ) else: # Can't use mkstemp - the file must not exist first try: @@ -214,72 +312,296 @@ class Library( BaseController ): msg=msg, messagetype='error' ) ) @web.expose - def library_dataset_dataset_association( self, trans, id=None, name=None, info=None, folder_id=None, - replace_id=None, refer_id=None, library_id=None, **kwd ): + def library_dataset( self, trans, id, library_id, **kwd ): + params = util.Params( kwd ) + msg = util.restore_text( params.get( 'msg', '' ) ) + messagetype = params.get( 'messagetype', 'done' ) + if params.get( 'permissions', False ): + action = 'permissions' + elif params.get( 'versions', False ): + action = 'versions' + else: + # 'information' will be the default + action = 'information' + library_dataset = trans.app.model.LibraryDataset.get( id ) + if not library_dataset: + msg = "Invalid library dataset specified, id: %s" %str( id ) + return trans.response.send_redirect( web.url_for( controller='library', + action='browse_library', + id=library_id, + msg=util.sanitize_text( msg ), + messagetype='error' ) ) + if action == 'information': + if params.get( 'edit_attributes_button', False ): + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, + library_item=library_dataset ): + if params.get( 'edit_attributes_button', False ): + old_name = library_dataset.name + new_name = util.restore_text( params.get( 'name', '' ) ) + new_info = util.restore_text( params.get( 'info', '' ) ) + if not new_name: + msg = 'Enter a valid name' + messagetype = 'error' + else: + library_dataset.name = new_name + library_dataset.info = new_info + library_dataset.flush() + msg = "Dataset '%s' has been renamed to '%s'" % ( old_name, new_name ) + messagetype = 'done' + else: + msg = "You are not authorized to change the attributes of this dataset" + messagetype = "error" + return trans.fill_template( '/library/library_dataset_info.mako', + library_dataset=library_dataset, + library_id=library_id, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) + elif action == 'versions': + if params.get( 'change_version_button', False ): + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, + library_item=library_dataset ): + target_lda = trans.app.model.LibraryDatasetDatasetAssociation.get( kwd.get( 'set_lda_id' ) ) + library_dataset.library_dataset_dataset_association = target_lda + trans.app.model.flush() + msg = 'The current version of this library dataset has been updated to be %s' % target_lda.name + messagetype = 'done' + else: + msg = "You are not authorized to change the versions of this dataset" + messagetype = "error" + return trans.fill_template( '/library/library_dataset_versions.mako', + library_dataset=library_dataset, + library_id=library_id, + msg=msg, + messagetype=messagetype ) + elif action == 'permissions': + if params.get( 'update_roles_button', False ): + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MANAGE, + library_item=library_dataset ): + # The user clicked the Save button on the 'Associate With Roles' form + permissions = {} + for k, v in trans.app.model.Library.permitted_actions.items(): + in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ] + permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles + # Set the LIBRARY permissions on the LibraryDataset + # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions + trans.app.security_agent.set_all_library_permissions( library_dataset, permissions ) + library_dataset.refresh() + # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation + trans.app.security_agent.set_all_library_permissions( library_dataset.library_dataset_dataset_association, permissions ) + library_dataset.library_dataset_dataset_association.refresh() + msg = 'Permissions and roles have been updated for library dataset %s' % library_dataset.name + messagetype = 'done' + else: + msg = "You are not authorized to managed the permissions of this dataset" + messagetype = "error" + return trans.fill_template( '/library/library_dataset_permissions.mako', + library_dataset=library_dataset, + library_id=library_id, + msg=msg, + messagetype=messagetype ) + @web.expose + def library_dataset_dataset_association( self, trans, library_id, folder_id, id=None, **kwd ): params = util.Params( kwd ) msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) dbkey = params.get( 'dbkey', '?' ) last_used_build = params.get( 'last_used_build', None ) - folder = replace_dataset = None - if folder_id: - folder = trans.app.model.LibraryFolder.get( folder_id ) - if not last_used_build: - last_used_build = folder.genome_build - permission_source = folder + folder = trans.app.model.LibraryFolder.get( folder_id ) + if not folder: + msg = "Invalid library folder id ( %s ) specified" % str( folder_id ) + return trans.response.send_redirect( web.url_for( controller='library', + action='browse_library', + id=library_id, + msg=util.sanitize_text( msg ), + messagetype='error' ) ) + if not last_used_build: + last_used_build = folder.genome_build try: replace_dataset = trans.app.model.LibraryDataset.get( params.get( 'replace_id', None ) ) if not last_used_build: last_used_build = replace_dataset.library_dataset_dataset_association.dbkey - permission_source = replace_dataset except: replace_dataset = None + # Let's not overwrite the imported datatypes module with the variable datatypes? + # The built-in 'id' is overwritten in lots of places as well + ldatatypes = [ x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys() ] + ldatatypes.sort() if id: if params.get( 'permissions', False ): action = 'permissions' else: # 'information' will be the default action = 'information' - # The user is updating the permissions on 1 or more datasets if id.count( ',' ): ids = id.split( ',' ) id = None else: ids = None - if id: - # id specified, display attributes form - ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( id ) - if not ldda: - msg = "Invalid LibraryDatasetDatasetAssociation specified, id: %s" % str( id ) - return trans.response.send_redirect( web.url_for( controller='library', - action='browse_library', - id=library_id, - msg=util.sanitize_text( msg ), - messagetype='error' ) ) - if action == 'permissions': - if params.get( 'update_roles_button', False ): - # The user clicked the Save button on the 'Associate With Roles' form + else: + ids = None + if id: + # ldda_id specified, display attributes form + ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( id ) + if not ldda: + msg = "Invalid LibraryDatasetDatasetAssociation specified, id: %s" % str( id ) + return trans.response.send_redirect( web.url_for( controller='library', + action='browse_library', + id=library_id, + msg=util.sanitize_text( msg ), + messagetype='error' ) ) + if action == 'permissions': + if params.get( 'update_roles_button', False ): + # The user clicked the Save button on the 'Associate With Roles' form + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MANAGE, + library_item=ldda ) and \ + trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS, + dataset=ldda.dataset ): permissions = {} for k, v in trans.app.model.Dataset.permitted_actions.items(): in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ] permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles + # Set the DATASET permissions on the Dataset trans.app.security_agent.set_all_dataset_permissions( ldda.dataset, permissions ) - # Set/display library security info + ldda.dataset.refresh() permissions = {} for k, v in trans.app.model.Library.permitted_actions.items(): - in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ] + in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ] permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles + # Set the LIBRARY permissions on the LibraryDataset + # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions + trans.app.security_agent.set_all_library_permissions( ldda.library_dataset, permissions ) + ldda.library_dataset.refresh() + # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation trans.app.security_agent.set_all_library_permissions( ldda, permissions ) - ldda.dataset.refresh() + ldda.refresh() msg = "Permissions updated for dataset '%s'" % ldda.name - return trans.response.send_redirect( web.url_for( controller='library', - action='library_dataset_dataset_association', - id=id, - library_id=library_id, - msg=util.sanitize_text( msg ), - messagetype='done' ) ) - return trans.fill_template( '/library/ldda_permissions.mako', ldda=ldda, library_id=library_id, msg=msg, messagetype=messagetype ) - elif action == 'information': + messagetype = 'done' + else: + msg = "You are not authorized to change the permissions of dataset '%s'" % ldda.name + messagetype = 'error' + return trans.fill_template( '/library/ldda_permissions.mako', + ldda=ldda, + library_id=library_id, + msg=msg, + messagetype=messagetype ) + elif action == 'information': + if params.get( 'change', False ): + # The user clicked the Save button on the 'Change data type' form + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, + library_item=ldda ): + trans.app.datatypes_registry.change_datatype( ldda, params.datatype ) + trans.app.model.flush() + msg = "Data type changed for library dataset '%s'" % ldda.name + messagetype = 'done' + else: + msg = "You are not authorized to change the data type of dataset '%s'" % ldda.name + messagetype = 'error' + return trans.fill_template( "/library/ldda_info.mako", + ldda=ldda, + library_id=library_id, + datatypes=ldatatypes, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) + elif params.get( 'save', False ): + # The user clicked the Save button on the 'Edit Attributes' form + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, + library_item=ldda ): + old_name = ldda.name + new_name = util.restore_text( params.get( 'name', '' ) ) + new_info = util.restore_text( params.get( 'info', '' ) ) + if not new_name: + msg = 'Enter a valid name' + messagetype = 'error' + else: + ldda.name = new_name + ldda.info = new_info + # The following for loop will save all metadata_spec items + for name, spec in ldda.datatype.metadata_spec.items(): + if spec.get("readonly"): + continue + optional = params.get( "is_" + name, None ) + if optional and optional == 'true': + # optional element... == 'true' actually means it is NOT checked (and therefore ommitted) + setattr( ldda.metadata, name, None ) + else: + setattr( ldda.metadata, name, spec.unwrap( params.get ( name, None ) ) ) + ldda.metadata.dbkey = dbkey + ldda.datatype.after_edit( ldda ) + trans.app.model.flush() + msg = 'Attributes updated for library dataset %s' % ldda.name + messagetype = 'done' + else: + msg = "you are not authorized to edit the attributes of dataset '%s'" % ldda.name + messagetype = 'error' + return trans.fill_template( "/library/ldda_info.mako", + ldda=ldda, + library_id=library_id, + datatypes=ldatatypes, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) + elif params.get( 'detect', False ): + # The user clicked the Auto-detect button on the 'Edit Attributes' form + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, + library_item=ldda ): + for name, spec in ldda.datatype.metadata_spec.items(): + # We need to be careful about the attributes we are resetting + if name not in [ 'name', 'info', 'dbkey' ]: + if spec.get( 'default' ): + setattr( ldda.metadata, name, spec.unwrap( spec.get( 'default' ) ) ) + ldda.datatype.set_meta( ldda ) + ldda.datatype.after_edit( ldda ) + trans.app.model.flush() + msg = 'Attributes updated for library dataset %s' % ldda.name + messagetype = 'done' + else: + msg = "you are not authorized to edit the attributes of dataset '%s'" % ldda.name + messagetype = 'error' + return trans.fill_template( "/library/ldda_info.mako", + ldda=ldda, + library_id=library_id, + datatypes=ldatatypes, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) + elif params.get( 'delete', False ): + # TODO: need to revamp the way we remove datasets from disk. + # The user selected the "Remove this dataset from the library" pop-up menu option + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, + library_item=folder ): + ldda.deleted = True + ldda.flush() + msg = 'Dataset %s has been removed from this library' % ldda.name + messagetype = 'done' + else: + msg = "you are not authorized to delete dataset '%s'" % ldda.name + messagetype = 'error' + return trans.fill_template( "/library/ldda_info.mako", + ldda=ldda, + library_id=library_id, + datatypes=ldatatypes, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, + library_item=ldda ): ldda.datatype.before_edit( ldda ) if "dbkey" in ldda.datatype.metadata_spec and not ldda.metadata.dbkey: # Copy dbkey into metadata, for backwards compatability @@ -288,100 +610,141 @@ class Library( BaseController ): # case it resorts to the old dbkey. Setting the dbkey # sets it properly in the metadata ldda.metadata.dbkey = ldda.dbkey - # let's not overwrite the imported datatypes module with the variable datatypes? - ### the built-in 'id' is overwritten in lots of places as well - ldatatypes = [x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys()] - ldatatypes.sort() - return trans.fill_template( "/library/ldda_info.mako", - ldda=ldda, - library_id=library_id, - datatypes=ldatatypes, - msg=msg, - messagetype=messagetype ) - elif ids: - # Multiple ids specfied, display permission form, permissions will be updated for all simultaneously. - lddas = [] - for id in [ int( id ) for id in ids ]: - ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( id ) - if ldda is None: - msg = 'You specified an invalid LibraryDatasetDatasetAssociation id: %s' %str( id ) - trans.response.send_redirect( web.url_for( controller='library', - action='browse_library', - id=library_id, - msg=util.sanitize_text( msg ), - messagetype='error' ) ) - lddas.append( ldda ) - if len( lddas ) < 2: - msg = 'You must specify at least two datasets on which to modify permissions, ids you sent: %s' % str( ids ) + return trans.fill_template( "/library/ldda_info.mako", + ldda=ldda, + library_id=library_id, + datatypes=ldatatypes, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), + msg=msg, + messagetype=messagetype ) + elif ids: + # Multiple ids specfied, display permission form, permissions will be updated for all simultaneously. + lddas = [] + for id in [ int( id ) for id in ids ]: + ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( id ) + if ldda is None: + msg = 'You specified an invalid LibraryDatasetDatasetAssociation id: %s' %str( id ) trans.response.send_redirect( web.url_for( controller='library', action='browse_library', id=library_id, msg=util.sanitize_text( msg ), messagetype='error' ) ) - if action == 'permissions': - if params.get( 'update_roles_button', False ): + lddas.append( ldda ) + if len( lddas ) < 2: + msg = 'You must specify at least two datasets on which to modify permissions, ids you sent: %s' % str( ids ) + trans.response.send_redirect( web.url_for( controller='library', + action='browse_library', + id=library_id, + msg=util.sanitize_text( msg ), + messagetype='error' ) ) + if action == 'permissions': + if params.get( 'update_roles_button', False ): + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MANAGE, + library_item=ldda ) and \ + trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS, + dataset=ldda.dataset ): permissions = {} for k, v in trans.app.model.Dataset.permitted_actions.items(): in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ] permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles for ldda in lddas: + # Set the DATASET permissions on the Dataset trans.app.security_agent.set_all_dataset_permissions( ldda.dataset, permissions ) ldda.dataset.refresh() + permissions = {} + for k, v in trans.app.model.Library.permitted_actions.items(): + in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ] + permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles + for ldda in lddas: + # Set the LIBRARY permissions on the LibraryDataset + # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions + trans.app.security_agent.set_all_library_permissions( ldda.library_dataset, permissions ) + ldda.library_dataset.refresh() + # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation + trans.app.security_agent.set_all_library_permissions( ldda, permissions ) + ldda.refresh() msg = 'Permissions and roles have been updated on %d datasets' % len( lddas ) - return trans.fill_template( "/library/ldda_permissions.mako", - ldda=lddas, - library_id=library_id, - msg=msg, - messagetype=messagetype ) - # Ensure that the permissions across all datasets are identical. Otherwise, we can't update together. - tmp = [] - for ldda in lddas: - perms = trans.app.security_agent.get_dataset_permissions( ldda.dataset ) - if perms not in tmp: - tmp.append( perms ) - if len( tmp ) != 1: - msg = 'The datasets you selected do not have identical permissions, so they can not be updated together' - trans.response.send_redirect( web.url_for( controller='library', - action='browse_library', - id=library_id, - msg=util.sanitize_text( msg ), - messagetype='error' ) ) - else: + messagetype = 'done' + else: + msg = "You are not authorized to change the permissions of dataset '%s'" % ldda.name + messagetype = 'error' return trans.fill_template( "/library/ldda_permissions.mako", ldda=lddas, library_id=library_id, msg=msg, messagetype=messagetype ) - if not folder and not replace_dataset: - msg = "Invalid library target specified (LibraryFolder id: %s, LibraryDataset id: %s)" % ( str( folder_id ), str( replace_id ) ) - return trans.response.send_redirect( web.url_for( controller='library', - action='browse_library', - id=library_id, - msg=util.sanitize_text( msg ), - messagetype='error' ) ) - if ( folder and trans.app.security_agent.allow_action( trans.user, - trans.app.security_agent.permitted_actions.LIBRARY_ADD, - library_item=folder ) ) or \ + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MANAGE, + library_item=ldda ) and \ + trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS, + dataset=ldda.dataset ): + # Ensure that the permissions across all library items are identical, otherwise we can't update them together. + check_list = [] + for ldda in lddas: + permissions = [] + # Check the library level permissions - the permissions on the LibraryDatasetDatasetAssociation + # will always be the same as the permissions on the associated LibraryDataset, so we only need to + # check one Library object + for library_permission in trans.app.security_agent.get_library_dataset_permissions( ldda.library_dataset ): + if library_permission.action not in permissions: + permissions.append( library_permission.action ) + for dataset_permission in trans.app.security_agent.get_dataset_permissions( ldda.dataset ): + if dataset_permission.action not in permissions: + permissions.append( dataset_permission.action ) + permissions.sort() + if not check_list: + check_list = permissions + if permissions != check_list: + msg = 'The datasets you selected do not have identical permissions, so they can not be updated together' + trans.response.send_redirect( web.url_for( controller='library', + action='browse_library', + id=library_id, + msg=util.sanitize_text( msg ), + messagetype='error' ) ) + else: + msg = "You are not authorized to change the permissions of dataset '%s'" % ldda.name + messagetype = 'error' + return trans.fill_template( "/library/ldda_permissions.mako", + ldda=lddas, + library_id=library_id, + msg=msg, + messagetype=messagetype ) + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_ADD, + library_item=folder ) or \ ( replace_dataset and trans.app.security_agent.allow_action( trans.user, - trans.app.security_agent.permitted_actions.LIBRARY_ADD, + trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, library_item=replace_dataset ) ): if params.get( 'new_dataset_button', False ): - created_ldda_ids = upload_dataset( trans, - controller='library', - folder_id=folder_id, - replace_dataset=replace_dataset, - permission_source=permission_source, - **kwd ) + created_ldda_ids = trans.webapp.controllers[ 'library_dataset' ].upload_dataset( trans, + controller='library', + library_id=library_id, + folder_id=folder_id, + replace_dataset=replace_dataset, + **kwd ) if created_ldda_ids: - total_added = len( created_ldda_ids.split( ',' ) ) + ldda_id_list = created_ldda_ids.split( ',' ) + total_added = len( ldda_id_list ) + if replace_dataset: + msg = "Added %d dataset versions to the library dataset '%s' in the folder '%s'." % ( total_added, replace_dataset.name, folder.name ) + else: + msg = "Added %d datasets to the library folder '%s' ( each is selected ). " % ( total_added, folder.name ) + # Since permissions on all LibraryDatasetDatasetAssociations must be the same at this point, we only need + # to check one of them to see if the current user can manage permissions on them. + check_ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id_list[0] ) if trans.app.security_agent.allow_action( trans.user, trans.app.security_agent.permitted_actions.LIBRARY_MANAGE, - library_item=permission_source ): - msg = "%i new datasets added to the library ( each is selected below ). " % total_added + \ - "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary." - default_action = 'manage_permissions' + library_item=check_ldda ): + if replace_dataset: + default_action = '' + else: + msg += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary." + default_action = 'manage_permissions' else: - msg = "%i new datasets added to the library." % total_added default_action = 'add' trans.response.send_redirect( web.url_for( controller='library', action='browse_library', @@ -399,148 +762,149 @@ class Library( BaseController ): created_ldda_ids=created_ldda_ids, msg=util.sanitize_text( msg ), messagetype='error' ) ) - elif params.get( "add_dataset_from_history_button", False): - # See if the current history is empty - history = trans.get_history() - history.refresh() - if not history.active_datasets: - msg = 'Your current history is empty' - return trans.response.send_redirect( web.url_for( controller='library', - action='browse_library', - id=library_id, - msg=util.sanitize_text( msg ), - messagetype='error' ) ) - hids = util.listify( params.get( 'hids', [] ) ) - dataset_names = [] - if hids: - for data_id in hids: - data = trans.app.model.HistoryDatasetAssociation.get( data_id ) - if data: - data = data.to_library_dataset_dataset_association( target_folder=folder ) - dataset_names.append( data.name ) - if folder: - folder.add_dataset( data ) - elif replace_dataset: - # If we are replacing versions and we receive a list, - # we add all the datasets and set the last one in the list as current - if trans.app.security_agent.allow_action( trans.user, - trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, - library_item=replace_dataset ): - replace_dataset.set_library_dataset_dataset_association( data ) - else: - data.library_dataset = replace_dataset - data.flush() - else: - msg = "The requested dataset id %s is invalid" % str( data_id ) - return trans.response.send_redirect( web.url_for( controller='library', - action='browse_library', - id=library_id, - msg=util.sanitize_text( msg ), - messagetype='error' ) ) - if dataset_names: - if folder: - msg = "Added %d datasets to the library folder '%s'" % ( len( dataset_names ), folder.name ) - else: - msg = "Added %d datasets to the versioned library dataset '%s'" % ( len( dataset_names ), replace_dataset.name ) - return trans.response.send_redirect( web.url_for( controller='library', - action='browse_library', - id=library_id, - msg=util.sanitize_text( msg ), - messagetype='done' ) ) - else: - # No dataset(s) specified, display upload form - file_formats = trans.app.datatypes_registry.upload_file_formats - # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically - def get_dbkey_options( last_used_build ): - for dbkey, build_name in util.dbnames: - yield build_name, dbkey, ( dbkey==last_used_build ) - dbkeys = get_dbkey_options( last_used_build ) - # Send list of roles to the form so the dataset can be associated with 1 or more of them. - roles = trans.app.model.Role.filter( trans.app.model.Role.table.c.deleted==False ).order_by( trans.app.model.Role.c.name ).all() - msg = 'Select at least one dataset from the list' - return trans.fill_template( "/library/new_dataset.mako", - history=history, - library_id=library_id, - folder_id=folder_id, - file_formats=file_formats, - dbkeys=dbkeys, - last_used_build=last_used_build, - roles=roles, - replace_dataset=replace_dataset, - msg=msg, - messagetype='error' ) - - #copied... - def get_dbkey_options( last_used_build ): - for dbkey, build_name in util.dbnames: - yield build_name, dbkey, ( dbkey==last_used_build ) - return trans.fill_template( "/library/new_dataset.mako", - library_id=library_id, - folder_id=folder_id, - replace_dataset=replace_dataset, - file_formats=trans.app.datatypes_registry.upload_file_formats, - dbkeys=get_dbkey_options( '?' ), - last_used_build=last_used_build, - msg=msg, - messagetype=messagetype ) + if not id or replace_dataset: + upload_option = params.get( 'upload_option', 'upload_file' ) + # No dataset(s) specified, so display the upload form. Send list of data formats to the form + # so the "extension" select list can be populated dynamically + file_formats = trans.app.datatypes_registry.upload_file_formats + # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically + def get_dbkey_options( last_used_build ): + for dbkey, build_name in util.dbnames: + yield build_name, dbkey, ( dbkey==last_used_build ) + dbkeys = get_dbkey_options( last_used_build ) + # Send list of roles to the form so the dataset can be associated with 1 or more of them. + roles = trans.app.model.Role.filter( trans.app.model.Role.table.c.deleted==False ).order_by( trans.app.model.Role.c.name ).all() + # Send the current history to the form to enable importing datasets from history to library + history = trans.get_history() + history.refresh() + return trans.fill_template( '/library/new_dataset.mako', + upload_option=upload_option, + library_id=library_id, + folder_id=folder_id, + file_formats=file_formats, + dbkeys=dbkeys, + last_used_build=last_used_build, + roles=roles, + history=history, + msg=msg, + messagetype=messagetype, + replace_dataset=replace_dataset ) @web.expose - def library_dataset( self, trans, id, name=None, info=None, refer_id=None, library_id=None, **kwd ): + def add_history_datasets_to_library( self, trans, library_id, folder_id, hda_ids='', **kwd ): params = util.Params( kwd ) msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) - library_dataset = trans.app.model.LibraryDataset.get( id ) - if not library_dataset: - msg = "Invalid library dataset specified, id: %s" %str( id ) + try: + folder = trans.app.model.LibraryFolder.get( int( folder_id ) ) + except: + msg = "Invalid folder id: %s" % str( folder_id ) return trans.response.send_redirect( web.url_for( controller='library', action='browse_library', id=library_id, msg=util.sanitize_text( msg ), messagetype='error' ) ) - if refer_id: - refered_lda = trans.app.model.LibraryDatasetDatasetAssociation.get( refer_id ) - else: - refered_lda=None - if params.get( 'save', False ): - if trans.app.security_agent.allow_action( trans.user, - trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, - library_item=library_dataset ): - library_dataset.name = name - library_dataset.info = info - target_lda = trans.app.model.LibraryDatasetDatasetAssociation.get( params.get( 'set_lda_id' ) ) - library_dataset.library_dataset_dataset_association = target_lda - trans.app.model.flush() - msg = 'Attributes updated for library dataset %s' % library_dataset.name - messagetype = 'done' - else: - msg = "You are not authorized to changed the attributes of this dataset" - messagetype = "error" + try: + replace_dataset = trans.app.model.LibraryDataset.get( params.get( 'replace_id', None ) ) + except: + replace_dataset = None + # See if the current history is empty + history = trans.get_history() + history.refresh() + if not history.active_datasets: + msg = 'Your current history is empty' return trans.response.send_redirect( web.url_for( controller='library', - action='library_dataset', - id=id, - library_id=library_id, + action='browse_library', + id=library_id, msg=util.sanitize_text( msg ), - messagetype=messagetype ) ) - elif 'update_roles_button' in kwd: - if trans.app.security_agent.allow_action( trans.user, - trans.app.security_agent.permitted_actions.LIBRARY_MANAGE, - library_item=library_dataset ): - # The user clicked the Save button on the 'Associate With Roles' form - permissions = {} - for k, v in trans.app.model.Library.permitted_actions.items(): - in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ] - permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles - trans.app.security_agent.set_all_library_permissions( library_dataset, permissions ) - library_dataset.refresh() - msg = 'Permissions updated for library dataset %s' % library_dataset.name - else: - msg = "Permission Denied" - messagetype = "error" - return trans.fill_template( "/library/manage_library_dataset.mako", - library_dataset=library_dataset, - refered_lda=refered_lda, - library_id=library_id, - msg=msg, - messagetype=messagetype ) + messagetype='error' ) ) + if params.get( 'add_history_datasets_to_library_button', False ): + hda_ids = util.listify( hda_ids ) + if hda_ids: + dataset_names = [] + created_ldda_ids = '' + for hda_id in hda_ids: + hda = trans.app.model.HistoryDatasetAssociation.get( hda_id ) + if hda: + ldda = hda.to_library_dataset_dataset_association( target_folder=folder ) + created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( ldda.id ) ) + dataset_names.append( ldda.name ) + if replace_dataset: + # If we are replacing versions and we receive a list, we add all the datasets + # and set the last one in the list as current + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, + library_item=replace_dataset ): + replace_dataset.set_library_dataset_dataset_association( ldda ) + else: + ldda.library_dataset = replace_dataset + # Copy the LDDA and LibraryDataset level permissions from replace_dataset to the new LDDA and LibraryDataset + trans.app.security_agent.copy_library_permissions( replace_dataset.library_dataset_dataset_association, ldda ) + trans.app.security_agent.copy_library_permissions( replace_dataset.library_dataset, ldda.library_dataset ) + else: + # If replace_dataset is None, the Library level permissions will be taken from the folder and applied to the new + # LDDA and LibraryDataset. + trans.app.security_agent.copy_library_permissions( folder, ldda ) + trans.app.security_agent.copy_library_permissions( folder, ldda.library_dataset ) + else: + msg = "The requested HistoryDatasetAssociation id %s is invalid" % str( hda_id ) + return trans.response.send_redirect( web.url_for( controller='library', + action='browse_library', + id=library_id, + msg=util.sanitize_text( msg ), + messagetype='error' ) ) + if created_ldda_ids: + created_ldda_ids = created_ldda_ids.lstrip( ',' ) + ldda_id_list = created_ldda_ids.split( ',' ) + total_added = len( ldda_id_list ) + if replace_dataset: + msg = "Added %d dataset versions to the library dataset '%s' in the folder '%s'." % ( total_added, replace_dataset.name, folder.name ) + else: + msg = "Added %d datasets to the library folder '%s' ( each is selected ). " % ( total_added, folder.name ) + # Since permissions on all LibraryDatasetDatasetAssociations must be the same at this point, we only need + # to check one of them to see if the current user can manage permissions on them. + check_ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id_list[0] ) + if trans.app.security_agent.allow_action( trans.user, + trans.app.security_agent.permitted_actions.LIBRARY_MANAGE, + library_item=check_ldda ): + if replace_dataset: + default_action = '' + else: + msg += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary." + default_action = 'manage_permissions' + else: + default_action = 'add' + return trans.response.send_redirect( web.url_for( controller='library', + action='browse_library', + id=library_id, + created_ldda_ids=created_ldda_ids.lstrip( ',' ), + default_action=default_action, + msg=util.sanitize_text( msg ), + messagetype='done' ) ) + else: + msg = 'Select at least one dataset from the list of active datasets in your current history' + messagetype = 'error' + last_used_build = folder.genome_build + upload_option = params.get( 'upload_option', 'upload_file' ) + # Send list of data formats to the form so the "extension" select list can be populated dynamically + file_formats = trans.app.datatypes_registry.upload_file_formats + # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically + def get_dbkey_options( last_used_build ): + for dbkey, build_name in util.dbnames: + yield build_name, dbkey, ( dbkey==last_used_build ) + dbkeys = get_dbkey_options( last_used_build ) + # Send list of roles to the form so the dataset can be associated with 1 or more of them. + roles = trans.app.model.Role.filter( trans.app.model.Role.table.c.deleted==False ).order_by( trans.app.model.Role.c.name ).all() + return trans.fill_template( "/library/new_dataset.mako", + upload_option=upload_option, + library_id=library_id, + folder_id=folder_id, + file_formats=file_formats, + dbkeys=dbkeys, + last_used_build=last_used_build, + roles=roles, + history=history, + msg=msg, + messagetype=messagetype ) @web.expose def folder( self, trans, id, library_id, **kwd ): params = util.Params( kwd ) @@ -557,7 +921,7 @@ class Library( BaseController ): action = 'information' folder = trans.app.model.LibraryFolder.get( int( id ) ) if not folder: - msg = "Invalid folder specified, id: %s" %str( id ) + msg = "Invalid folder specified, id: %s" % str( id ) return trans.response.send_redirect( web.url_for( controller='library', action='browse_library', id=library_id, @@ -565,8 +929,6 @@ class Library( BaseController ): messagetype='error' ) ) if action == 'new': if params.new == 'submitted': - # Create the new folder, then return manage_folder template for new folder. - # New folders default to having the same permissions as their parent folder new_folder = trans.app.model.LibraryFolder( name=util.restore_text( params.name ), description=util.restore_text( params.description ) ) # We are associating the last used genome build with folders, so we will always @@ -575,13 +937,12 @@ class Library( BaseController ): new_folder.genome_build = util.dbnames.default_value folder.add_folder( new_folder ) new_folder.flush() + # New folders default to having the same permissions as their parent folder trans.app.security_agent.copy_library_permissions( folder, new_folder, user=trans.get_user() ) msg = "New folder named '%s' has been added to the library" % new_folder.name return trans.response.send_redirect( web.url_for( controller='library', - action='folder', - id=id, - library_id=library_id, - information=True, + action='browse_library', + id=library_id, msg=util.sanitize_text( msg ), messagetype='done' ) ) return trans.fill_template( '/library/new_folder.mako', @@ -602,6 +963,8 @@ class Library( BaseController ): return trans.fill_template( "/library/folder_info.mako", folder=folder, library_id=library_id, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), msg=msg, messagetype='error' ) else: @@ -621,11 +984,15 @@ class Library( BaseController ): return trans.fill_template( "/library/folder_info.mako", folder=folder, library_id=library_id, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), msg=msg, messagetype='error' ) return trans.fill_template( '/library/folder_info.mako', folder=folder, library_id=library_id, + restrict=params.get( 'restrict', True ), + render_templates=params.get( 'render_templates', False ), msg=msg, messagetype=messagetype ) elif action == 'permissions': @@ -663,266 +1030,265 @@ class Library( BaseController ): msg=msg, messagetype=messagetype ) @web.expose - def library( self, trans, id=None, **kwd ): + def info_template( self, trans, library_id, id=None, num_fields=0, folder_id=None, ldda_id=None, library_dataset_id=None, **kwd ): params = util.Params( kwd ) msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) - if not id: - msg = "You must specify a library." - return trans.response.send_redirect( web.url_for( controller='library', - action='browse_libraries', - msg=util.sanitize_text( msg ), - messagetype='error' ) ) - library = trans.app.model.Library.get( int( id ) ) - if not library: - msg = "Invalid library id ( %s ) specified." % str( id ) - return trans.response.send_redirect( web.url_for( controller='library', - action='browse_libraries', - msg=util.sanitize_text( msg ), - messagetype='error' ) ) - if params.get( 'delete', False ): - action = 'delete' - elif params.get( 'permissions', False ): - action = 'permissions' + if not num_fields: + num_fields = 0 else: - # 'information' is the default - action = 'information' - if action == 'information': - if params.get( 'rename_library_button', False ): - old_name = library.name - new_name = util.restore_text( params.name ) - new_description = util.restore_text( params.description ) - if not new_name: - msg = 'Enter a valid name' - return trans.fill_template( '/library/library_info.mako', library=library, msg=msg, messagetype='error' ) + num_fields = int( num_fields ) + if params.get( 'new_template', False ): + action = 'new_template' + else: + action = 'edit_template' + if folder_id: + library_item = trans.app.model.LibraryFolder.get( int( folder_id ) ) + library_item_desc = 'folder' + response_action = 'folder' + response_id = folder_id + elif ldda_id: + library_item = trans.app.model.LibraryDatasetDatasetAssociation.get( int( ldda_id ) ) + library_item_desc = 'library dataset <-> dataset association' + response_action = 'library_dataset_dataset_association' + response_id = ldda_id + elif library_dataset_id: + library_item = trans.app.model.LibraryDataset.get( int( library_dataset_id ) ) + library_item_desc = 'library dataset' + response_action = 'library_dataset_dataset_association' + response_id = library_item.library_dataset_dataset_association.id + else: + library_item = trans.app.model.Library.get( int( library_id ) ) + library_item_desc = 'library' + response_action = 'browse_library' + response_id = library_id + if action == 'new_template': + if params.get( 'create_info_template_button', False ): + return trans.fill_template( '/library/new_info_template.mako', + library_item=library_item, + library_item_name=library_item.name, + library_item_desc=library_item_desc, + num_fields=num_fields, + library_id=library_id, + folder_id=folder_id, + ldda_id=ldda_id, + library_dataset_id=library_dataset_id, + msg=msg, + messagetype=messagetype ) + elif params.get( 'new_info_template_button', False ): + # Make sure at least 1 template field is filled in + # TODO: Eventually we'll enhance templates to allow for required and optional fields. + proceed = False + for i in range( int( params.get( 'set_num_fields', 0 ) ) ): + elem_name = params.get( 'new_element_name_%i' % i, None ) + elem_description = params.get( 'new_element_description_%i' % i, None ) + if elem_name or elem_description: + proceed = True + break + if not proceed: + msg = "At least 1 of the fields must be completed." + return trans.fill_template( '/library/new_info_template.mako', + library_item=library_item, + library_item_name=library_item.name, + library_item_desc=library_item_desc, + num_fields=num_fields, + library_id=library_id, + folder_id=folder_id, + ldda_id=ldda_id, + library_dataset_id=library_dataset_id, + msg=msg, + messagetype=messagetype ) + # Create template then display edit screen + liit = trans.app.model.LibraryItemInfoTemplate() + liit.name = util.restore_text( params.get( 'name', '' ) ) + liit.description = util.restore_text( params.get( 'description', '' ) ) + liit.flush() + # Create template association + if folder_id: + liit_assoc = trans.app.model.LibraryFolderInfoTemplateAssociation() + liit_assoc.folder = trans.app.model.LibraryFolder.get( folder_id ) + elif ldda_id: + liit_assoc = trans.app.model.LibraryDatasetDatasetInfoTemplateAssociation() + ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id ) + liit_assoc.library_dataset_dataset_association = ldda + # This response_action method requires a folder_id + folder_id = ldda.library_dataset.folder.id + elif library_dataset_id: + liit_assoc = trans.app.model.LibraryDatasetInfoTemplateAssociation() + library_dataset = trans.app.model.LibraryDataset.get( library_dataset_id ) + liit_assoc.library_dataset = library_dataset + # This response_action method requires a folder_id + folder_id = library_dataset.folder.id else: - if params.get( 'root_folder', False ): - root_folder = library.root_folder - root_folder.name = new_name - root_folder.flush() - library.name = new_name - library.description = new_description - library.flush() - msg = "Library '%s' has been renamed to '%s'" % ( old_name, new_name ) - return trans.response.send_redirect( web.url_for( controller='library', - action='library', - id=id, - information=True, - msg=util.sanitize_text( msg ), - messagetype='done' ) ) - return trans.fill_template( '/library/library_info.mako', library=library, msg=msg, messagetype=messagetype ) - elif action == 'permissions': - if params.get( 'update_roles_button', False ): - # The user clicked the Save button on the 'Associate With Roles' form - permissions = {} - for k, v in trans.app.model.Library.permitted_actions.items(): - in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ] - permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles - trans.app.security_agent.set_all_library_permissions( library, permissions ) - library.refresh() - msg = "Permissions updated for library '%s'" % library.name + # We'll always be sent a library_id + liit_assoc = trans.app.model.LibraryInfoTemplateAssociation() + liit_assoc.library = trans.app.model.Library.get( library_id ) + liit_assoc.library_item_info_template = liit + liit_assoc.flush() + # Create and add elements + for i in range( int( params.get( 'set_num_fields', 0 ) ) ): + elem_name = params.get( 'new_element_name_%i' % i, None ) + elem_description = params.get( 'new_element_description_%i' % i, None ) + if elem_description and not elem_name: + # If we have a description but no name, the description will be both + # ( a name cannot be empty, but a description can ) + elem_name = elem_description + if elem_name: + # Skip any elements that have a missing name + liit.add_element( name=elem_name, description=elem_description ) + msg = "The new information template has been created." return trans.response.send_redirect( web.url_for( controller='library', - action='library', - id=id, - permissions=True, + action=response_action, + id=response_id, + library_id=library_id, + folder_id=folder_id, msg=util.sanitize_text( msg ), messagetype='done' ) ) - return trans.fill_template( '/admin/library/manage_library_permissions.mako', library=library, msg=msg, messagetype=messagetype ) - @web.expose - def library_item_info_template( self, trans, id=None, new_element_count=0, library_id=None, - folder_id=None, library_dataset_id=None, ldda_id=None, **kwd ): - params = util.Params( kwd ) - msg = util.restore_text( params.get( 'msg', '' ) ) - messagetype = params.get( 'messagetype', 'done' ) - new_element_count = int( new_element_count ) - liit = None - if id: + return trans.fill_template( '/library/create_info_template.mako', + library_item=library_item, + library_id=library_id, + msg=msg, + messagetype=messagetype ) + elif action == 'edit_template': try: - liit = trans.app.model.LibraryItemInfoTemplate.get( id ) + liit = trans.app.model.LibraryItemInfoTemplate.get( int( id ) ) except: msg = "Invalid library info template specified, id: %s" % str( id ) - # TODO: this should probably redirect to the library_dataset_dataset_association method... - return trans.response.send_redirect( web.url_for( controller='admin', - action='browse_library', - id=library_id, + return trans.response.send_redirect( web.url_for( controller='library', + action=response_action, + id=response_id, + library_id=library_id, msg=util.sanitize_text( msg ), messagetype='error' ) ) - if params.get( 'liit_create_button', False ): - # Create template then display edit screen - liit = trans.app.model.LibraryItemInfoTemplate() - liit.name = params.get( 'name', 'unnamed' ) - liit.description = params.get( 'description', '' ) - liit.flush() - # Create template association - if library_id: - liit_assoc = trans.app.model.LibraryInfoTemplateAssociation() - liit_assoc.library = trans.app.model.Library.get( library_id ) - elif folder_id: - liit_assoc = trans.app.model.LibraryFolderInfoTemplateAssociation() - liit_assoc.folder = trans.app.model.LibraryFolder.get( folder_id ) - elif library_dataset_id: - liit_assoc = trans.app.model.LibraryDatasetInfoTemplateAssociation() - liit_assoc.library_dataset = trans.app.model.LibraryDataset.get( library_dataset_id ) - elif ldda_id: - liit_assoc = trans.app.model.LibraryDatasetDatasetInfoTemplateAssociation() - liit_assoc.library_dataset_dataset_association = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id ) - liit_assoc.library_item_info_template = liit - liit_assoc.flush() - # Create and add elements - for i in range( int( params.get( 'set_element_count', 0 ) ) ): - elem_name = params.get( 'new_element_name_%i' % i, None ) - elem_description = params.get( 'new_element_description_%i' % i, None ) - # Skip any elements that have a missing name and description - if not elem_name: - # If we have a description but no name, the description will be both - # ( a name cannot be empty, but a description can ) - elem_name = elem_description - else: - liit.add_element( name=elem_name, description=elem_description ) - elif params.get( 'liit_edit_button', False ): - # Save changes to existing attributes, only set name if nonempty/nonNone is passed, but always set description - name = params.get( 'name', None ) - if name: - liit.name = name - liit.description = params.get( 'description', '' ) - liit.flush() - # Save changes to exisiting elements - for elem_id in params.get( 'element_ids', [] ): - liit_element = trans.app.model.LibraryItemInfoTemplateElement.get( elem_id ) - name = params.get( 'element_name_%s' % elem_id, None ) + if params.get( 'edit_info_template_button', False ): + # Save changes to existing attributes, only set name if nonempty/nonNone is passed, but always set description + name = params.get( 'name', None ) if name: - liit_element.name = name - liit_element.description = params.get( 'element_description_%s' % elem_id, None ) - liit_element.flush() - # Add new elements - for i in range( int( params.get( 'set_element_count', 0 ) ) ): - elem_name = params.get( 'new_element_name_%i' % i, None ) - elem_description = params.get( 'new_element_description_%i' % i, None ) - # Skip any elements that have a missing name and description - if not elem_name: - # If we have a description but no name, the description will be both - # ( a name cannot be empty, but a description can ) - elem_name = elem_description - if elem_name: - liit.add_element( name=elem_name, description=elem_description ) - liit.refresh() - msg = 'Information template %s has been updated' % liit.name - if library_id: - library_item_name = trans.app.model.Library.get( library_id ).name - library_item_desc = 'library' - elif folder_id: - library_item_name = trans.app.model.LibraryFolder.get( folder_id ).name - library_item_desc = 'folder' - elif library_dataset_id: - ld = trans.app.model.LibraryDataset.get( library_dataset_id ) - library_item_name = ld.name - library_item_desc = 'library dataset' - elif ldda_id: - library_item_name = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id ).name - library_item_desc = 'library dataset <-> dataset association' - else: - library_item_name = 'unknown' - library_item_desc = '' - return trans.fill_template( "/library/item_info_template.mako", - liit=liit, - new_element_count=new_element_count, - library_id=library_id, - library_dataset_id=library_dataset_id, - ldda_id=ldda_id, - folder_id=folder_id, - library_item_name=library_item_name, - library_item_desc=library_item_desc, - msg=msg, - messagetype=messagetype ) + liit.name = name + liit.description = params.get( 'description', '' ) + liit.flush() + # Save changes to exisiting elements + for elem_id in params.get( 'element_ids', [] ): + liit_element = trans.app.model.LibraryItemInfoTemplateElement.get( elem_id ) + name = params.get( 'element_name_%s' % elem_id, None ) + if name: + liit_element.name = name + liit_element.description = params.get( 'element_description_%s' % elem_id, None ) + liit_element.flush() + # Add new elements + for i in range( int( params.get( 'set_num_fields', 0 ) ) ): + elem_name = params.get( 'new_element_name_%i' % i, None ) + elem_description = params.get( 'new_element_description_%i' % i, None ) + # Skip any elements that have a missing name and description + if not elem_name: + # If we have a description but no name, the description will be both + # ( a name cannot be empty, but a description can ) + elem_name = elem_description + if elem_name: + liit.add_element( name=elem_name, description=elem_description ) + liit.refresh() + msg = 'Information template %s has been updated' % liit.name + return trans.fill_template( "/library/edit_info_template.mako", + liit=liit, + num_fields=num_fields, + library_id=library_id, + library_dataset_id=library_dataset_id, + ldda_id=ldda_id, + folder_id=folder_id, + library_item_name=library_item.name, + library_item_desc=library_item_desc, + msg=msg, + messagetype=messagetype ) @web.expose - def library_item_info( self, trans, do_action='display', id=None, library_item_id=None, library_item_type=None, library_id=None, **kwd ): + def library_item_info( self, trans, library_id, id=None, library_item_id=None, library_item_type=None, **kwd ): params = util.Params( kwd ) msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) + folder_id = None if id: item_info = trans.app.model.LibraryItemInfo.get( id ) else: item_info = None if library_item_type == 'library': library_item = trans.app.model.Library.get( library_item_id ) - error_action = 'browse' elif library_item_type == 'library_dataset': library_item = trans.app.model.LibraryDataset.get( library_item_id ) - error_action = 'library_dataset' elif library_item_type == 'folder': library_item = trans.app.model.LibraryFolder.get( library_item_id ) elif library_item_type == 'library_dataset_dataset_association': - error_action = 'folder' library_item = trans.app.model.LibraryDatasetDatasetAssociation.get( library_item_id ) - error_action = 'library_dataset_dataset_association' + # This response_action method requires a folder_id + folder_id = library_item.library_dataset.folder.id else: library_item_type == None library_item = None - if not item_info and not library_item_type: - msg = "Unable to perform requested action (%s)." % do_action + if not library_item: + msg = "Invalid library item type ( %s ) specified, id ( %s )" % ( str( library_item_type ), str( library_item_id ) ) return trans.response.send_redirect( web.url_for( controller='library', - action=error_action, - id=library_item_id, + action='browse_library', + id=library_id, msg=util.sanitize_text( msg ), messagetype='error' ) ) - #TODO: not sure we need the display option or the display_info.mako template any longer... - if do_action == 'display': - return trans.fill_template( "/library/display_info.mako", - item_info=item_info, - msg=msg, - messagetype=messagetype ) - elif do_action == 'new_info': + if params.get( 'new_info', False ): if library_item: - if trans.app.security_agent.allow_action( trans.user, - trans.app.security_agent.permitted_actions.LIBRARY_ADD, - library_item=library_item ): - if params.get( 'create_new_info_button', False ): - user = trans.get_user() - #create new info then send back to make more - library_item_info_template_id = params.get( 'library_item_info_template_id', None ) - library_item_info_template = trans.app.model.LibraryItemInfoTemplate.get( library_item_info_template_id ) - library_item_info = trans.app.model.LibraryItemInfo() - library_item_info.library_item_info_template = library_item_info_template - library_item_info.user = user - library_item_info.flush() - trans.app.security_agent.copy_library_permissions( library_item_info_template, library_item_info, user=user ) - for template_element in library_item_info_template.elements: - info_element_value = params.get( "info_element_%s_%s" % ( library_item_info_template.id, template_element.id), None ) - info_element = trans.app.model.LibraryItemInfoElement() - info_element.contents = info_element_value - info_element.library_item_info_template_element = template_element - info_element.library_item_info = library_item_info - info_element.flush() - info_association_class = None - for item_class, permission_class, info_association_class in trans.app.security_agent.library_item_assocs: - if isinstance( library_item, item_class ): - break - if info_association_class: - library_item_info_association = info_association_class() - library_item_info_association.set_library_item( library_item ) - library_item_info_association.library_item_info = library_item_info - library_item_info_association.user = user - library_item_info_association.flush() - else: - raise 'Invalid class (%s) specified for library_item (%s)' % ( library_item.__class__, library_item.__class__.__name__ ) - # TODO: make sure we don't need to set permissions on the association object. - msg = 'The information has been saved. You can add more information if necessary.' - return trans.response.send_redirect( web.url_for( controller='library', + if params.get( 'create_new_info_button', False ): + library_item_info_template_id = params.get( 'library_item_info_template_id', None ) + library_item_info_template = trans.app.model.LibraryItemInfoTemplate.get( int( library_item_info_template_id ) ) + # Make sure at least 1 template field is filled in + # TODO: Eventually we'll enhance templates to allow for required and optional fields. + proceed = False + for template_element in library_item_info_template.elements: + if params.get( "info_element_%s_%s" % ( library_item_info_template.id, template_element.id ), None ): + proceed = True + break + if not proceed: + msg = "At least 1 of the fields must be completed." + return trans.response.send_redirect( web.url_for( controller='admin', action=library_item_type, id=library_item.id, library_id=library_id, + folder_id=folder_id, + information=True, msg=util.sanitize_text( msg ), - messagetype='done' ) ) - return trans.fill_template( "/library/new_info.mako", - library_item=library_item, - library_item_type=library_item_type, - library_id=library_id, - msg=msg, - messagetype=messagetype ) - else: - return trans.show_error_message( "You do not have permission to add information to this library item." ) + messagetype='error' ) ) + user = trans.get_user() + library_item_info = trans.app.model.LibraryItemInfo() + library_item_info.library_item_info_template = library_item_info_template + library_item_info.user = user + library_item_info.flush() + trans.app.security_agent.copy_library_permissions( library_item_info_template, library_item_info ) + for template_element in library_item_info_template.elements: + info_element_value = params.get( "info_element_%s_%s" % ( library_item_info_template.id, template_element.id ), None ) + info_element = trans.app.model.LibraryItemInfoElement() + info_element.contents = info_element_value + info_element.library_item_info_template_element = template_element + info_element.library_item_info = library_item_info + info_element.flush() + info_association_class = None + for item_class, permission_class, info_association_class in trans.app.security_agent.library_item_assocs: + if isinstance( library_item, item_class ): + break + if info_association_class: + library_item_info_association = info_association_class() + library_item_info_association.set_library_item( library_item, trans.user ) + library_item_info_association.library_item_info = library_item_info + library_item_info_association.user = user + library_item_info_association.flush() + else: + raise 'Invalid class (%s) specified for library_item (%s)' % ( library_item.__class__, library_item.__class__.__name__ ) + msg = 'The information has been saved.' + return trans.response.send_redirect( web.url_for( controller='library', + action=library_item_type, + id=library_item.id, + library_id=library_id, + folder_id=folder_id, + information=True, + msg=util.sanitize_text( msg ), + messagetype='done' ) ) + return trans.fill_template( "/library/new_info.mako", + library_id=library_id, + library_item=library_item, + library_item_type=library_item_type, + msg=msg, + messagetype=messagetype ) # TODO: add more functionality -> user's should be able to edit/delete, etc, and create/delete and edit templates - return trans.fill_template( "/library/display_info.mako", - item_info=item_info, - msg="Unable to perform requested action (%s)." % do_action, - messagetype=messagetype ) diff --git a/lib/galaxy/web/controllers/library_dataset.py b/lib/galaxy/web/controllers/library_dataset.py new file mode 100644 index 00000000000..e72824420f6 --- /dev/null +++ b/lib/galaxy/web/controllers/library_dataset.py @@ -0,0 +1,355 @@ +import os, shutil, urllib, StringIO, re, gzip, tempfile, shutil, zipfile +from galaxy.web.base.controller import * +from galaxy import util, jobs +from galaxy.datatypes import sniff +from galaxy.security import RBACAgent + +log = logging.getLogger( __name__ ) + +class UploadLibraryDataset( BaseController ): + def remove_tempfile( self, filename ): + try: + os.unlink( filename ) + except: + log.exception( 'failure removing temporary file: %s' % filename ) + def add_file( self, trans, folder_id, file_obj, name, file_format, dbkey, roles, info='no info', space_to_tab=False, replace_dataset=None ): + folder = trans.app.model.LibraryFolder.get( folder_id ) + data_type = None + line_count = 0 + temp_name = sniff.stream_to_file( file_obj ) + # See if we have an empty file + if not os.path.getsize( temp_name ) > 0: + raise BadFileException( "you attempted to upload an empty file." ) + # See if we have a gzipped file, which, if it passes our restrictions, we'll uncompress on the fly. + is_gzipped, is_valid = self.check_gzip( temp_name ) + if is_gzipped and not is_valid: + raise BadFileException( "you attempted to upload an inappropriate file." ) + elif is_gzipped and is_valid: + # We need to uncompress the temp_name file + CHUNK_SIZE = 2**20 # 1Mb + fd, uncompressed = tempfile.mkstemp() + gzipped_file = gzip.GzipFile( temp_name ) + while 1: + try: + chunk = gzipped_file.read( CHUNK_SIZE ) + except IOError: + os.close( fd ) + os.remove( uncompressed ) + raise BadFileException( 'problem uncompressing gzipped data.' ) + if not chunk: + break + os.write( fd, chunk ) + os.close( fd ) + gzipped_file.close() + # Replace the gzipped file with the decompressed file + shutil.move( uncompressed, temp_name ) + name = name.rstrip( '.gz' ) + data_type = 'gzip' + if not data_type: + # See if we have a zip archive + is_zipped, is_valid, test_ext = self.check_zip( temp_name ) + if is_zipped and not is_valid: + raise BadFileException( "you attempted to upload an inappropriate file." ) + elif is_zipped and is_valid: + # Currently, we force specific tools to handle this case. We also require the user + # to manually set the incoming file_format + if ( test_ext == 'ab1' or test_ext == 'scf' ) and file_format != 'binseq.zip': + raise BadFileException( "Invalid 'File Format' for archive consisting of binary files - use 'Binseq.zip'." ) + elif test_ext == 'txt' and file_format != 'txtseq.zip': + raise BadFileException( "Invalid 'File Format' for archive consisting of text files - use 'Txtseq.zip'." ) + if not ( file_format == 'binseq.zip' or file_format == 'txtseq.zip' ): + raise BadFileException( "you must manually set the 'File Format' to either 'Binseq.zip' or 'Txtseq.zip' when uploading zip files." ) + data_type = 'zip' + ext = file_format + if not data_type: + if self.check_binary( temp_name ): + ext = file_name.split( "." )[1].strip().lower() + if not( ext == 'ab1' or ext == 'scf' ): + raise BadFileException( "you attempted to upload an inappropriate file." ) + if ext == 'ab1' and file_format != 'ab1': + raise BadFileException( "you must manually set the 'File Format' to 'Ab1' when uploading ab1 files." ) + elif ext == 'scf' and file_format != 'scf': + raise BadFileException( "you must manually set the 'File Format' to 'Scf' when uploading scf files." ) + data_type = 'binary' + if not data_type: + # We must have a text file + if self.check_html( temp_name ): + raise BadFileException( "you attempted to upload an inappropriate file." ) + if data_type != 'binary' and data_type != 'zip': + if space_to_tab: + line_count = sniff.convert_newlines_sep2tabs( temp_name ) + elif os.stat( temp_name ).st_size < 262144000: # 250MB + line_count = sniff.convert_newlines( temp_name ) + else: + if sniff.check_newlines( temp_name ): + line_count = sniff.convert_newlines( temp_name ) + else: + line_count = None + if file_format == 'auto': + ext = sniff.guess_ext( temp_name, sniff_order=trans.app.datatypes_registry.sniff_order ) + else: + ext = file_format + data_type = ext + if info is None: + info = 'uploaded %s file' % data_type + if file_format == 'auto': + data_type = sniff.guess_ext( temp_name, sniff_order=trans.app.datatypes_registry.sniff_order ) + else: + data_type = file_format + if replace_dataset: + # The replace_dataset param ( when not None ) refers to a LibraryDataset that is being replaced with a new version. + # In this case, all of the permissions on the expired LibraryDataset will be applied to the new version. + library_dataset = replace_dataset + else: + # If replace_dataset is None, the Library level permissions will be taken from the folder and applied to the new + # LibraryDataset, and the current user's DefaultUserPermissions will be applied to the associated Dataset. + library_dataset = trans.app.model.LibraryDataset( folder=folder, name=name, info=info ) + library_dataset.flush() + trans.app.security_agent.copy_library_permissions( folder, library_dataset ) + ldda = trans.app.model.LibraryDatasetDatasetAssociation( name=name, + info=info, + extension=data_type, + dbkey=dbkey, + library_dataset=library_dataset, + create_dataset=True ) + ldda.flush() + # Permissions must be the same on the LibraryDatasetDatasetAssociation and the associated LibraryDataset + trans.app.security_agent.copy_library_permissions( library_dataset, ldda ) + if replace_dataset: + # Copy the Dataset level permissions from replace_dataset to the new LibraryDatasetDatasetAssociation.dataset + trans.app.security_agent.copy_dataset_permissions( replace_dataset.library_dataset_dataset_association.dataset, ldda.dataset ) + else: + # Copy the current user's DefaultUserPermissions to the new LibraryDatasetDatasetAssociation.dataset + trans.app.security_agent.set_all_dataset_permissions( ldda.dataset, trans.app.security_agent.user_get_default_permissions( trans.get_user() ) ) + folder.add_library_dataset( library_dataset, genome_build=dbkey ) + library_dataset.library_dataset_dataset_association_id = ldda.id + library_dataset.flush() + # If roles were selected upon upload, restrict access to the Dataset to those roles + if roles: + for role in roles: + dp = trans.app.model.DatasetPermissions( RBACAgent.permitted_actions.DATASET_ACCESS.action, ldda.dataset, role ) + dp.flush() + shutil.move( temp_name, ldda.dataset.file_name ) + ldda.state = ldda.states.OK + ldda.init_meta() + if line_count: + try: + ldda.set_peek( line_count=line_count ) + except: + ldda.set_peek() + else: + ldda.set_peek() + ldda.set_size() + if ldda.missing_meta(): + ldda.datatype.set_meta( ldda ) + ldda.flush() + return ldda + @web.expose + def upload_dataset( self, trans, controller, library_id, folder_id, replace_dataset=None, **kwd ): + # This method is called from both the admin and library controllers. The replace_dataset param ( when + # not None ) refers to a LibraryDataset that is being replaced with a new version. + params = util.Params( kwd ) + msg = util.restore_text( params.get( 'msg', '' ) ) + messagetype = params.get( 'messagetype', 'done' ) + dbkey = params.get( 'dbkey', '?' ) + file_format = params.get( 'file_format', 'auto' ) + data_file = params.get( 'file_data', '' ) + url_paste = params.get( 'url_paste', '' ) + server_dir = params.get( 'server_dir', 'None' ) + if replace_dataset is not None: + replace_id = replace_dataset.id + else: + replace_id = None + if data_file == '' and url_paste == '' and server_dir in [ 'None', '' ]: + if trans.app.config.library_import_dir is not None: + msg = 'Select a file, enter a URL or Text, or select a server directory.' + else: + msg = 'Select a file, enter a URL or enter Text.' + trans.response.send_redirect( web.url_for( controller=controller, + action='library_dataset_dataset_association', + library_id=library_id, + folder_id=folder_id, + replace_id=replace_id, + msg=util.sanitize_text( msg ), + messagetype='done' ) ) + space_to_tab = params.get( 'space_to_tab', False ) + if space_to_tab and space_to_tab not in [ "None", None ]: + space_to_tab = True + roles = [] + for role_id in util.listify( params.get( 'roles', [] ) ): + roles.append( trans.app.model.Role.get( role_id ) ) + data_list = [] + created_ldda_ids = '' + if 'filename' in dir( data_file ): + file_name = data_file.filename + file_name = file_name.split( '\\' )[-1] + file_name = file_name.split( '/' )[-1] + try: + created_ldda = self.add_file( trans, + folder_id, + data_file.file, + file_name, + file_format, + dbkey, + roles, + info="uploaded file", + space_to_tab=space_to_tab, + replace_dataset=replace_dataset ) + created_ldda_ids = str( created_ldda.id ) + except Exception, e: + log.exception( 'exception in upload_dataset using file_name %s: %s' % ( str( file_name ), str( e ) ) ) + return self.upload_empty( trans, controller, library_id, "Error:", str( e ) ) + elif url_paste not in [ None, "" ]: + if url_paste.lower().find( 'http://' ) >= 0 or url_paste.lower().find( 'ftp://' ) >= 0: + url_paste = url_paste.replace( '\r', '' ).split( '\n' ) + for line in url_paste: + line = line.rstrip( '\r\n' ) + if line: + try: + created_ldda = self.add_file( trans, + folder_id, + urllib.urlopen( line ), + line, + file_format, + dbkey, + roles, + info="uploaded url", + space_to_tab=space_to_tab, + replace_dataset=replace_dataset ) + created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( created_ldda.id ) ) + except Exception, e: + log.exception( 'exception in upload_dataset using url_paste %s' % str( e ) ) + return self.upload_empty( trans, controller, library_id, "Error:", str( e ) ) + else: + is_valid = False + for line in url_paste: + line = line.rstrip( '\r\n' ) + if line: + is_valid = True + break + if is_valid: + try: + created_ldda = self.add_file( trans, + folder_id, + StringIO.StringIO( url_paste ), + 'Pasted Entry', + file_format, + dbkey, + roles, + info="pasted entry", + space_to_tab=space_to_tab, + replace_dataset=replace_dataset ) + created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( created_ldda.id ) ) + except Exception, e: + log.exception( 'exception in add_file using StringIO.StringIO( url_paste ) %s' % str( e ) ) + return self.upload_empty( trans, controller, library_id, "Error:", str( e ) ) + elif server_dir not in [ None, "", "None" ]: + full_dir = os.path.join( trans.app.config.library_import_dir, server_dir ) + try: + files = os.listdir( full_dir ) + except: + log.debug( "Unable to get file list for %s" % full_dir ) + for file in files: + full_file = os.path.join( full_dir, file ) + if not os.path.isfile( full_file ): + continue + try: + created_ldda = self.add_file( trans, + folder_id, + open( full_file, 'rb' ), + file, + file_format, + dbkey, + roles, + info="imported file", + space_to_tab=space_to_tab, + replace_dataset=replace_dataset ) + created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( created_ldda.id ) ) + except Exception, e: + log.exception( 'exception in add_file using server_dir %s' % str( e ) ) + return self.upload_empty( trans, controller, library_id, "Error:", str( e ) ) + if created_ldda_ids: + created_ldda_ids = created_ldda_ids.lstrip( ',' ) + return created_ldda_ids + else: + return '' + def check_gzip( self, temp_name ): + temp = open( temp_name, "U" ) + magic_check = temp.read( 2 ) + temp.close() + if magic_check != util.gzip_magic: + return ( False, False ) + CHUNK_SIZE = 2**15 # 32Kb + gzipped_file = gzip.GzipFile( temp_name ) + chunk = gzipped_file.read( CHUNK_SIZE ) + gzipped_file.close() + if self.check_html( temp_name, chunk=chunk ) or self.check_binary( temp_name, chunk=chunk ): + return( True, False ) + return ( True, True ) + def check_zip( self, temp_name ): + if not zipfile.is_zipfile( temp_name ): + return ( False, False, None ) + zip_file = zipfile.ZipFile( temp_name, "r" ) + # Make sure the archive consists of valid files. The current rules are: + # 1. Archives can only include .ab1, .scf or .txt files + # 2. All file file_formats within an archive must be the same + name = zip_file.namelist()[0] + test_ext = name.split( "." )[1].strip().lower() + if not ( test_ext == 'scf' or test_ext == 'ab1' or test_ext == 'txt' ): + return ( True, False, test_ext ) + for name in zip_file.namelist(): + ext = name.split( "." )[1].strip().lower() + if ext != test_ext: + return ( True, False, test_ext ) + return ( True, True, test_ext ) + def check_html( self, temp_name, chunk=None ): + if chunk is None: + temp = open(temp_name, "U") + else: + temp = chunk + regexp1 = re.compile( "]*HREF[^>]+>", re.I ) + regexp2 = re.compile( "]*>", re.I ) + regexp3 = re.compile( "]*>", re.I ) + regexp4 = re.compile( "]*>", re.I ) + lineno = 0 + for line in temp: + lineno += 1 + matches = regexp1.search( line ) or regexp2.search( line ) or regexp3.search( line ) or regexp4.search( line ) + if matches: + if chunk is None: + temp.close() + return True + if lineno > 100: + break + if chunk is None: + temp.close() + return False + def check_binary( self, temp_name, chunk=None ): + if chunk is None: + temp = open( temp_name, "U" ) + else: + temp = chunk + lineno = 0 + for line in temp: + lineno += 1 + line = line.strip() + if line: + for char in line: + if ord( char ) > 128: + if chunk is None: + temp.close() + return True + if lineno > 10: + break + if chunk is None: + temp.close() + return False + def upload_empty( self, trans, controller, library_id, err_code, err_msg ): + msg = err_code + err_msg + return trans.response.send_redirect( web.url_for( controller=controller, + action='browse_library', + id=library_id, + msg=util.sanitize_text( msg ), + messagetype='error' ) ) +class BadFileException( Exception ): + pass diff --git a/lib/galaxy/web/controllers/root.py b/lib/galaxy/web/controllers/root.py index 357d1511c2d..780cc42fd39 100644 --- a/lib/galaxy/web/controllers/root.py +++ b/lib/galaxy/web/controllers/root.py @@ -250,7 +250,7 @@ class RootController( BaseController ): if name not in [ 'name', 'info', 'dbkey' ]: if spec.get( 'default' ): setattr( data.metadata, name, spec.unwrap( spec.get( 'default' ) ) ) - data.datatype.set_meta( data ) + data.set_meta() data.datatype.after_edit( data ) trans.app.model.flush() return trans.show_ok_message( "Attributes updated", refresh_frames=['history'] ) @@ -492,24 +492,24 @@ class RootController( BaseController ): except: return trans.show_error_message( "

Failed to make secondary dataset primary.

" ) - @web.expose - def masthead( self, trans, active_view=None ): - brand = trans.app.config.get( "brand", "" ) - if brand: - brand ="/%s" % brand - wiki_url = trans.app.config.get( "wiki_url", "http://g2.trac.bx.psu.edu/" ) - bugs_email = trans.app.config.get( "bugs_email", "mailto:galaxy-bugs@bx.psu.edu" ) - blog_url = trans.app.config.get( "blog_url", "http://g2.trac.bx.psu.edu/blog" ) - screencasts_url = trans.app.config.get( "screencasts_url", "http://g2.trac.bx.psu.edu/wiki/ScreenCasts" ) - admin_user = "false" - admin_users = trans.app.config.get( "admin_users", "" ).split( "," ) - user = trans.get_user() - if user: - user_email = trans.get_user().email - if user_email in admin_users: - admin_user = "true" - return trans.fill_template( "/root/masthead.mako", brand=brand, wiki_url=wiki_url, - blog_url=blog_url,bugs_email=bugs_email, screencasts_url=screencasts_url, admin_user=admin_user, active_view=active_view ) + # @web.expose + # def masthead( self, trans, active_view=None ): + # brand = trans.app.config.get( "brand", "" ) + # if brand: + # brand ="/%s" % brand + # wiki_url = trans.app.config.get( "wiki_url", "http://g2.trac.bx.psu.edu/" ) + # bugs_email = trans.app.config.get( "bugs_email", "mailto:galaxy-bugs@bx.psu.edu" ) + # blog_url = trans.app.config.get( "blog_url", "http://g2.trac.bx.psu.edu/blog" ) + # screencasts_url = trans.app.config.get( "screencasts_url", "http://g2.trac.bx.psu.edu/wiki/ScreenCasts" ) + # admin_user = "false" + # admin_users = trans.app.config.get( "admin_users", "" ).split( "," ) + # user = trans.get_user() + # if user: + # user_email = trans.get_user().email + # if user_email in admin_users: + # admin_user = "true" + # return trans.fill_template( "/root/masthead.mako", brand=brand, wiki_url=wiki_url, + # blog_url=blog_url,bugs_email=bugs_email, screencasts_url=screencasts_url, admin_user=admin_user, active_view=active_view ) @web.expose def dataset_errors( self, trans, id=None, **kwd ): diff --git a/lib/galaxy/web/framework/helpers/__init__.py b/lib/galaxy/web/framework/helpers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/galaxy/web/framework/helpers/grids.py b/lib/galaxy/web/framework/helpers/grids.py new file mode 100644 index 00000000000..7c17b6d7af8 --- /dev/null +++ b/lib/galaxy/web/framework/helpers/grids.py @@ -0,0 +1,162 @@ +from galaxy.model import * +from galaxy.model.orm import * + +from galaxy.web import url_for + +import sys + +class Grid( object ): + """ + Specifieds the content and format of a grid (data table). + """ + title = "" + exposed = True + model_class = None + columns = [] + standard_filters = [] + default_filter = None + default_sort_key = None + def __init__( self ): + pass + def __call__( self, trans, **kwargs ): + # Session + session = trans.sa_session + # Process any actions + status = message = None + ids = self.get_ids( **kwargs ) + operation = kwargs.get( 'operation', None ) + if operation: + operation = operation.lower() + status, message = self.handle_operation( trans, operation, ids ) + # Build initial query + query = self.build_initial_query( session ) + query = self.apply_default_filter( trans, query ) + # Maintain sort state in generated urls + extra_url_args = {} + # Process filtering arguments + filter_args = {} + if self.default_filter: + filter_args.update( self.default_filter ) + for column in self.columns: + if column.key: + if "f-" + column.key in kwargs: + column_filter = kwargs.get( "f-" + column.key ) + if column_filter == "True": + filter_args[column.key] = True + elif column_filter == "False": + filter_args[column.key] = False + elif column_filter == "All": + del filter_args[column.key] + # Carry filter along to newly generated urls + extra_url_args[ "f-" + column.key ] = column_filter + if filter_args: + query = query.filter_by( **filter_args ) + # Process sort arguments + sort_key = sort_order = None + if 'sort' in kwargs: + sort_key = kwargs['sort'] + elif self.default_sort_key: + sort_key = self.default_sort_key + encoded_sort_key = sort_key + if sort_key: + if sort_key.startswith( "-" ): + sort_key = sort_key[1:] + sort_order = 'desc' + query = query.order_by( self.model_class.c.get( sort_key ).desc() ) + else: + sort_order = 'asc' + query = query.order_by( self.model_class.c.get( sort_key ).asc() ) + extra_url_args['sort'] = encoded_sort_key + # There might be a current row + current_item = self.get_current_item( trans ) + # Render + def url( *args, **kwargs ): + new_kwargs = dict( extra_url_args ) + if len(args) > 0: + new_kwargs.update( args[0] ) + new_kwargs.update( kwargs ) + return url_for( **new_kwargs ) + return trans.fill_template( "grid.mako", + grid=self, + query=query, + sort_key=sort_key, + encoded_sort_key=encoded_sort_key, + sort_order=sort_order, + current_item=current_item, + ids = ids, + url = url, + message_type = status, + message = message ) + def get_ids( self, **kwargs ): + id = [] + if 'id' in kwargs: + id = kwargs['id'] + # Coerce ids to list + if not isinstance( id, list ): + id = id.split( "," ) + # Ensure ids are integers + try: + id = map( int, id ) + except: + error( "Invalid id" ) + return id + + # ---- Override these ---------------------------------------------------- + def handle_operation( self, trans, operation, ids ): + pass + def get_current_item( self, trans ): + return None + def build_initial_query( self, session ): + return session.query( self.model_class ) + def apply_default_filter( self, trans, query ): + return query + +class GridColumn( object ): + def __init__( self, label, key=None, method=None, format=None, link=None, attach_popup=False, visible=True, ncells=1 ): + self.label = label + self.key = key + self.method = method + self.format = format + self.link = link + self.attach_popup = attach_popup + self.visible = visible + self.ncells = ncells + # Currently can only sort of columns that have a database + # representation, not purely derived. + if self.key: + self.sortable = True + else: + self.sortable = False + def get_value( self, trans, grid, item ): + if self.method: + value = getattr( grid, self.method )( trans, item ) + elif self.key: + value = getattr( item, self.key ) + else: + value = None + if self.format: + value = self.format( value ) + return value + +class GridOperation( object ): + def __init__( self, label, key=None, condition=None, allow_multiple=True ): + self.label = label + self.key = key + self.allow_multiple = allow_multiple + self.condition = condition + def allowed( self, item ): + if self.condition: + return self.condition( item ) + else: + return True + +class GridColumnFilter( object ): + def __init__( self, label, args=None ): + self.label = label + self.args = args + def get_url_args( self ): + rval = {} + for k, v in self.args.items(): + rval[ "f-" + k ] = v + return rval + \ No newline at end of file diff --git a/scripts/set_metadata.py b/scripts/set_metadata.py new file mode 100644 index 00000000000..7cd7ad2cc0b --- /dev/null +++ b/scripts/set_metadata.py @@ -0,0 +1,38 @@ +""" +Execute an external process to set_meta() on a provided list of pickled datasets. + +This should not be called directly! Use the set_metadata.sh script in Galaxy's +top level directly. + +""" + +import os, sys, cPickle +assert sys.version_info[:2] >= ( 2, 4 ) + +new_path = [ os.path.join( os.getcwd(), "lib" ) ] +new_path.extend( sys.path[1:] ) # remove scripts/ from the path +sys.path = new_path + +from galaxy import eggs +import pkg_resources +import galaxy.model.mapping #need to load this before we unpickle, in order to setup properties assigned by the mappers +galaxy.model.Job() #this looks REAL stupid, but it is REQUIRED in order for SA to insert parameters into the classes defined by the mappers --> it appears that instantiating ANY mapper'ed class would suffice here +galaxy.datatypes.metadata.DATABASE_CONNECTION_AVAILABLE = False #Let metadata know that there is no database connection, and to just assume object ids are valid + +def __main__(): + file_path = sys.argv.pop( 1 ) + tmp_dir = sys.argv.pop( 1 ) + galaxy.model.Dataset.file_path = file_path + galaxy.datatypes.metadata.MetadataTempFile.tmp_dir = tmp_dir + for pickled_filenames in sys.argv[1:]: + pickled_filename_in, pickled_filename_kwds, pickled_filename_out, pickled_filename_results_code = pickled_filenames.split( ',' ) + try: + data = cPickle.load( open( pickled_filename_in ) )#unpickle DatasetInstance + kwds = cPickle.load( open( pickled_filename_kwds ) )#unpickle kwds + data.datatype.set_meta( data, **kwds ) + data.metadata.to_pickled_dict( pickled_filename_out ) # write out results of set_meta + cPickle.dump( ( True, 'Metadata has been set successfully' ), open( pickled_filename_results_code, 'wb+' ) ) #setting metadata has suceeded + except Exception, e: + cPickle.dump( ( False, e ), open( pickled_filename_results_code, 'wb+' ) ) #setting metadata has failed somehow + +__main__() \ No newline at end of file diff --git a/scripts/update_database/update_database_with_security_libraries.py b/scripts/update_database/update_database_with_security_libraries.py index 4145107242f..547c2cda683 100644 --- a/scripts/update_database/update_database_with_security_libraries.py +++ b/scripts/update_database/update_database_with_security_libraries.py @@ -131,7 +131,12 @@ def main(): i.create() except Exception, e: print_warning( "Adding index failed: %s" % ( e ) ) - + try: + i = sqlalchemy.Index( 'ix_job_state', app.model.Job.table.c.state ) + i.create() + except Exception, e: + print_warning( "Adding index failed: %s" % ( e ) ) + # Shutdown the app app.shutdown() @@ -165,8 +170,8 @@ def main(): # For each user: # 1. make sure they have a private role # 2. set DefaultUserPermissions - # 3. set DefaultHisstoryPermissions on existing histories - # 4. set DatasetPermissionss on each history's activatable_datasets + # 3. set DefaultHistoryPermissions on existing histories + # 4. set DatasetPermissions on each history's activatable_datasets default_user_action = security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS.action for user in app.model.User.query().all(): @@ -175,7 +180,7 @@ def main(): print "Setting up user %s." % user.email private_role = security_agent.get_private_user_role( user ) if not private_role: - security_agent.create_private_user_role( user ) + private_role = security_agent.create_private_user_role( user ) print "Created private role for %s" % user.email else: print_warning( "%s already has a private role, re-setting defaults anyway" % user.email ) @@ -187,15 +192,8 @@ def main(): # Add the new default permissions for the user dup = app.model.DefaultUserPermissions( user, default_user_action, private_role ) dup.flush() - # Set DefaultHistoryPermissions on all of the user's active histories and associated datasets - # TODO: fix mapping for history.activatable_datasets so it doesn't throw an exception on the - # following query when eagerloading activatable_datasets. Fix all of the queries below that - # call history.active_datasets to be history.activatable_datasets when this works. - histories = app.model.History.filter( and_( app.model.History.table.c.user_id==user.id, - app.model.History.table.c.purged==False ) ) \ - .options( eagerload( 'active_datasets' ) ).all() - print "Setting DefaultHistoryPermissions for %d un-purged histories associated with %s" % ( len( histories ), user.email ) - for history in histories: + print "Setting DefaultHistoryPermissions for %d un-purged histories associated with %s" % ( len( user.histories ), user.email ) + for history in user.active_histories: # Delete all of the current default permissions for the history for dhp in history.default_permissions: dhp.delete() @@ -203,9 +201,10 @@ def main(): # Add the new default permissions for the history dhp = app.model.DefaultHistoryPermissions( history, default_user_action, private_role ) dhp.flush() - print "Setting DatasetPermissionss for %d un-purged datasets in history %d" % ( len( history.active_datasets ), history.id ) + activatable_datasets = history.activatable_datasets #store this list, so we don't generate it more than once + print "Setting DatasetPermissions for %d un-purged datasets in history %d" % ( len( activatable_datasets ), history.id ) # Set the permissions on the current history's datasets that are not purged - for hda in history.active_datasets: + for hda in activatable_datasets: dataset = hda.dataset if dataset.library_associations: # Don't change permissions on a dataset associated with a library diff --git a/scripts/yuicompressor.jar b/scripts/yuicompressor.jar index 3f92c3413c7..c29470bd0d0 100644 Binary files a/scripts/yuicompressor.jar and b/scripts/yuicompressor.jar differ diff --git a/set_metadata.sh b/set_metadata.sh new file mode 100755 index 00000000000..962c799e75c --- /dev/null +++ b/set_metadata.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cd `dirname $0` +python -ES ./scripts/set_metadata.py $@ diff --git a/static/fastx_icons/barcode_splitter_output_example.png b/static/fastx_icons/barcode_splitter_output_example.png new file mode 100644 index 00000000000..49dfd6c4cdb Binary files /dev/null and b/static/fastx_icons/barcode_splitter_output_example.png differ diff --git a/static/fastx_icons/fasta_clipping_histogram_1.png b/static/fastx_icons/fasta_clipping_histogram_1.png new file mode 100644 index 00000000000..a9416f68d9d Binary files /dev/null and b/static/fastx_icons/fasta_clipping_histogram_1.png differ diff --git a/static/fastx_icons/fasta_clipping_histogram_2.png b/static/fastx_icons/fasta_clipping_histogram_2.png new file mode 100644 index 00000000000..471be686bc1 Binary files /dev/null and b/static/fastx_icons/fasta_clipping_histogram_2.png differ diff --git a/static/fastx_icons/fastq_nucleotides_distribution_1.png b/static/fastx_icons/fastq_nucleotides_distribution_1.png new file mode 100644 index 00000000000..f238587ad02 Binary files /dev/null and b/static/fastx_icons/fastq_nucleotides_distribution_1.png differ diff --git a/static/fastx_icons/fastq_nucleotides_distribution_2.png b/static/fastx_icons/fastq_nucleotides_distribution_2.png new file mode 100644 index 00000000000..e935576e64c Binary files /dev/null and b/static/fastx_icons/fastq_nucleotides_distribution_2.png differ diff --git a/static/fastx_icons/fastq_nucleotides_distribution_3.png b/static/fastx_icons/fastq_nucleotides_distribution_3.png new file mode 100644 index 00000000000..cae0c53a88c Binary files /dev/null and b/static/fastx_icons/fastq_nucleotides_distribution_3.png differ diff --git a/static/fastx_icons/fastq_nucleotides_distribution_4.png b/static/fastx_icons/fastq_nucleotides_distribution_4.png new file mode 100644 index 00000000000..7f8369e57f4 Binary files /dev/null and b/static/fastx_icons/fastq_nucleotides_distribution_4.png differ diff --git a/static/fastx_icons/fastq_quality_boxplot_1.png b/static/fastx_icons/fastq_quality_boxplot_1.png new file mode 100644 index 00000000000..c3621fabf6c Binary files /dev/null and b/static/fastx_icons/fastq_quality_boxplot_1.png differ diff --git a/static/fastx_icons/fastq_quality_boxplot_2.png b/static/fastx_icons/fastq_quality_boxplot_2.png new file mode 100644 index 00000000000..95af2c1a648 Binary files /dev/null and b/static/fastx_icons/fastq_quality_boxplot_2.png differ diff --git a/static/fastx_icons/fastq_quality_boxplot_3.png b/static/fastx_icons/fastq_quality_boxplot_3.png new file mode 100644 index 00000000000..6121c84edbe Binary files /dev/null and b/static/fastx_icons/fastq_quality_boxplot_3.png differ diff --git a/static/fastx_icons/fastx_clipper_example.png b/static/fastx_icons/fastx_clipper_example.png new file mode 100644 index 00000000000..8d4ff4cc21a Binary files /dev/null and b/static/fastx_icons/fastx_clipper_example.png differ diff --git a/static/fastx_icons/fastx_clipper_illustration.png b/static/fastx_icons/fastx_clipper_illustration.png new file mode 100644 index 00000000000..5acf892fac9 Binary files /dev/null and b/static/fastx_icons/fastx_clipper_illustration.png differ diff --git a/static/june_2007_style/base.css.tmpl b/static/june_2007_style/base.css.tmpl index fc06c2008ff..e0c9bb86022 100644 --- a/static/june_2007_style/base.css.tmpl +++ b/static/june_2007_style/base.css.tmpl @@ -65,22 +65,20 @@ div.toolParamHelp a div.toolFormBody { - background: $form_body_bg_bottom; - background-image: url(form_body_bg.png); - background-repeat: repeat-x; - background-position: top; - padding: 5px; - padding-left: 10px; - padding-right: 10px; + background: $form_body_bg_bottom; + background-image: url(form_body_bg.png); + background-repeat: repeat-x; + background-position: top; + padding: 5px 0; } div.toolFormBody div.toolFormTitle { background: transparent; - border: none; - font-weight: bold; - border-bottom: solid $form_border 1px; - margin-bottom: 5px; + border: none; + font-weight: bold; + border-bottom: solid $form_border 1px; + margin-bottom: 5px; } div.toolFormDisabled div.toolFormTitle { @@ -98,8 +96,8 @@ div.toolHelp div.toolHelpBody { - width: 100%; - overflow: auto; + width: 100%; + overflow: auto; } div.titleRow { @@ -111,9 +109,30 @@ div.titleRow { /* Forms */ +div.form +{ + border: solid $form_border 1px; +} + +div.form-title +{ + font-weight: bold; + padding: 5px 10px; + background: $form_title_bg_bottom; + background-image: url(form_title_bg.png); + background-repeat: repeat-x; + background-position: top; + border-bottom: solid $form_border 1px; +} + +div.form-body +{ + padding: 5px 0; +} + div.form-row { - margin-bottom: 1em; + padding: 5px 10px; } div.form-title-row @@ -130,7 +149,6 @@ div.repeat-group-item div.form-row-error { background: $error_message_bg; - padding: 5px; } div.form-row label @@ -140,14 +158,35 @@ div.form-row label margin-bottom: .2em; } +div.form-row-input +{ + float: left; + width: 300px; +} + +div.form-row-input > input +{ + max-width: 300px; +} + +div.form-row-error-message +{ + width: 300px; + float: left; + color: red; + font-weight: bold; + padding: 3px 0 0 1em; +} + select, input, textarea { font: inherit; font-size: 115%; } -select, textarea, input[type="text"], input[type="file"] +select, textarea, input[type="text"], input[type="file"], input[type="password"] { + -webkit-box-sizing: border-box; max-width: 300px; } @@ -467,13 +506,26 @@ ul.manage-table-actions li { background: $history_running_bg; } +.state-fg-queued { + color: #888888; +} +.state-fg-ok { + color: #66AA66; +} +.state-fg-running { + color: #AAAA66; +} +.state-fg-error { + color: #AA6666; +} + ## Button styles .action-button { background: #eeeeee; color: #333; text-decoration: none; - font-size: 90%; + font-size: 95%; font-weight: bold; display: inline-block; cursor: pointer; @@ -508,13 +560,11 @@ div.popupmenu { display: none; background: #eeeeee; color: #333; - font-size: 90%; font-weight: bold; position: absolute; z-index: 20000; border: solid #aaaaaa 1px; - padding-top: 3px; - padding-bottom: 3px; + padding: 3px 0; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; border-radius: 0.5em; @@ -524,9 +574,7 @@ div.popupmenu { } div.popupmenu-item { - padding: 3px; - padding-left: 1em; - padding-right: 1em; + padding: 3px 1em; cursor: pointer; } @@ -551,6 +599,19 @@ div.permissionContainer { ## Data grid style +.grid-header { + padding-bottom: 1em; +} + +.grid-header h2 { + margin: 0; + margin-bottom: 0.5em; +} + +.grid-header .title { + font-weight: bold; +} + .grid { padding-top: 1em; border-collapse: collapse; @@ -561,6 +622,9 @@ div.permissionContainer { border-bottom: solid #DDDDDD 1px; padding: 0.5em 1em; } +.grid tbody td:empty { + padding: 0; +} .grid thead th { background: $table_header_bg; background-image: url(form_title_bg.png); diff --git a/static/june_2007_style/blue/base.css b/static/june_2007_style/blue/base.css index 2962cfb40b2..4d68feb9571 100644 --- a/static/june_2007_style/blue/base.css +++ b/static/june_2007_style/blue/base.css @@ -65,22 +65,20 @@ div.toolParamHelp a div.toolFormBody { - background: #FFFFFF; - background-image: url(form_body_bg.png); - background-repeat: repeat-x; - background-position: top; - padding: 5px; - padding-left: 10px; - padding-right: 10px; + background: #FFFFFF; + background-image: url(form_body_bg.png); + background-repeat: repeat-x; + background-position: top; + padding: 5px 0; } div.toolFormBody div.toolFormTitle { background: transparent; - border: none; - font-weight: bold; - border-bottom: solid #d8b365 1px; - margin-bottom: 5px; + border: none; + font-weight: bold; + border-bottom: solid #d8b365 1px; + margin-bottom: 5px; } div.toolFormDisabled div.toolFormTitle { @@ -98,8 +96,8 @@ div.toolHelp div.toolHelpBody { - width: 100%; - overflow: auto; + width: 100%; + overflow: auto; } div.titleRow { @@ -111,9 +109,30 @@ div.titleRow { /* Forms */ +div.form +{ + border: solid #d8b365 1px; +} + +div.form-title +{ + font-weight: bold; + padding: 5px 10px; + background: #d2c099; + background-image: url(form_title_bg.png); + background-repeat: repeat-x; + background-position: top; + border-bottom: solid #d8b365 1px; +} + +div.form-body +{ + padding: 5px 0; +} + div.form-row { - margin-bottom: 1em; + padding: 5px 10px; } div.form-title-row @@ -130,7 +149,6 @@ div.repeat-group-item div.form-row-error { background: #FFCCCC; - padding: 5px; } div.form-row label @@ -140,14 +158,35 @@ div.form-row label margin-bottom: .2em; } +div.form-row-input +{ + float: left; + width: 300px; +} + +div.form-row-input > input +{ + max-width: 300px; +} + +div.form-row-error-message +{ + width: 300px; + float: left; + color: red; + font-weight: bold; + padding: 3px 0 0 1em; +} + select, input, textarea { font: inherit; font-size: 115%; } -select, textarea, input[type="text"], input[type="file"] +select, textarea, input[type="text"], input[type="file"], input[type="password"] { + -webkit-box-sizing: border-box; max-width: 300px; } @@ -465,12 +504,25 @@ ul.manage-table-actions li { background: #FFFFCC; } +.state-fg-queued { + color: #888888; +} +.state-fg-ok { + color: #66AA66; +} +.state-fg-running { + color: #AAAA66; +} +.state-fg-error { + color: #AA6666; +} + .action-button { background: #eeeeee; color: #333; text-decoration: none; - font-size: 90%; + font-size: 95%; font-weight: bold; display: inline-block; cursor: pointer; @@ -504,13 +556,11 @@ div.popupmenu { display: none; background: #eeeeee; color: #333; - font-size: 90%; font-weight: bold; position: absolute; z-index: 20000; border: solid #aaaaaa 1px; - padding-top: 3px; - padding-bottom: 3px; + padding: 3px 0; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; border-radius: 0.5em; @@ -520,9 +570,7 @@ div.popupmenu { } div.popupmenu-item { - padding: 3px; - padding-left: 1em; - padding-right: 1em; + padding: 3px 1em; cursor: pointer; } @@ -546,6 +594,19 @@ div.permissionContainer { } +.grid-header { + padding-bottom: 1em; +} + +.grid-header h2 { + margin: 0; + margin-bottom: 0.5em; +} + +.grid-header .title { + font-weight: bold; +} + .grid { padding-top: 1em; border-collapse: collapse; @@ -556,6 +617,9 @@ div.permissionContainer { border-bottom: solid #DDDDDD 1px; padding: 0.5em 1em; } +.grid tbody td:empty { + padding: 0; +} .grid thead th { background: #ebd9b2; background-image: url(form_title_bg.png); diff --git a/static/june_2007_style/blue/panel_layout.css b/static/june_2007_style/blue/panel_layout.css index e48f52009af..77b4fa60a14 100644 --- a/static/june_2007_style/blue/panel_layout.css +++ b/static/june_2007_style/blue/panel_layout.css @@ -23,27 +23,6 @@ body width: 100%; height: 100%; } -#masthead -{ - position:absolute; - top:0; - left:0; - width:100%; - height:32px; - background: url(masthead_bg.png); - overflow: hidden; - color:#fff; - font-weight: bold; - border-bottom: solid #444 1px; -} - -#masthead div.title -{ - margin: 3px; - margin-right: 10px; - font-size: 182%; - font-weight: bold; -} #messagebox { @@ -254,4 +233,101 @@ div.unified-panel-body { { background-image: url(info_small.png); background-color: #CCCCFF; -} \ No newline at end of file +} + + +#masthead +{ + position:absolute; + top:0; + left:0; + width:100%; + height:32px; + background: #2C3143; + color:#fff; + border-bottom: solid #444 1px; + z-index: 15000; + padding: 0; +} + +#masthead a +{ + color: #eeeeee; +} + +#masthead .title +{ + padding: 3px 10px; + font-size: 175%; + font-weight: bold; +} + +#masthead a +{ + text-decoration: none; +} + +#masthead a:hover +{ + text-decoration: underline; +} + +div.tab-group +{ + margin: 0; + padding: 0 10px; + height: 100%; + white-space: nowrap; +} + +div.tab-group span.tab +{ + position: relative; + display: block; + float: left; + margin: 0; + padding: 0 1em; + height: 32px; + line-height: 32px; + text-align: left; +} + +div.tab-group > span.tab:hover > a +{ + color: gold !important; +} + +div.tab-group > span.active +{ + background: rgb(1,1,1); +} + +div.tab-group span.tab div.submenu { + display: none; + position: absolute; + z-index: 16000; + left: 0; + top: 32px; + padding: 1em; + margin: -1em; + padding-top: 0; + margin-top: 0; + background-color: rgba(0,0,0,0.5); + -moz-border-radius: 0 0 1em 1em; + -webkit-border-bottom-right-radius: 1em; + -webkit-border-bottom-left-radius: 1em; +} + +div.tab-group span.tab div.submenu ul { + display: block; + margin: 0; + padding: 0; + list-style-type: none; + background: #2C3143; +} + +div.tab-group span.tab div.submenu ul li { + display: block; + padding: 0 1em; + white-space: nowrap; +} diff --git a/static/june_2007_style/blue/reset.css b/static/june_2007_style/blue/reset.css index 0eb970e56bf..1c958c2a9b6 100644 --- a/static/june_2007_style/blue/reset.css +++ b/static/june_2007_style/blue/reset.css @@ -1,3 +1,3 @@ -/* Copyright (c) 2007, Yahoo! Inc. All rights reserved. */ -body {font:13px arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}table {font-size:inherit;font:100%;}select, input, textarea {font:99% arial,helvetica,clean,sans-serif;}pre, code {font:115% monospace;*font-size:100%;}body * {line-height:1.22em;} +/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. */ +body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;} /*body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}ol,ul {list-style:none;}caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;}*/ diff --git a/static/june_2007_style/panel_layout.css.tmpl b/static/june_2007_style/panel_layout.css.tmpl index 016b228a0e0..81da6eff586 100644 --- a/static/june_2007_style/panel_layout.css.tmpl +++ b/static/june_2007_style/panel_layout.css.tmpl @@ -23,27 +23,6 @@ body width: 100%; height: 100%; } -#masthead -{ - position:absolute; - top:0; - left:0; - width:100%; - height:32px; - background: url(masthead_bg.png); - overflow: hidden; - color:#fff; - font-weight: bold; - border-bottom: solid ${layout_masthead_border} 1px; -} - -#masthead div.title -{ - margin: 3px; - margin-right: 10px; - font-size: 182%; - font-weight: bold; -} #messagebox { @@ -255,4 +234,102 @@ div.unified-panel-body { { background-image: url(info_small.png); background-color: $info_message_bg; -} \ No newline at end of file +} + +## ---- Masthead styles ---- + +#masthead +{ + position:absolute; + top:0; + left:0; + width:100%; + height:32px; + background: ${masthead_bg}; + color:#fff; + border-bottom: solid ${layout_masthead_border} 1px; + z-index: 15000; + padding: 0; +} + +#masthead a +{ + color: $masthead_link; +} + +#masthead .title +{ + padding: 3px 10px; + font-size: 175%; + font-weight: bold; +} + +#masthead a +{ + text-decoration: none; +} + +#masthead a:hover +{ + text-decoration: underline; +} + +div.tab-group +{ + margin: 0; + padding: 0 10px; + height: 100%; + white-space: nowrap; +} + +div.tab-group span.tab +{ + position: relative; + display: block; + float: left; + margin: 0; + padding: 0 1em; + height: 32px; + line-height: 32px; + text-align: left; +} + +div.tab-group > span.tab:hover > a +{ + color: gold !important; +} + +div.tab-group > span.active +{ + background: rgb(1,1,1); +} + +div.tab-group span.tab div.submenu { + display: none; + position: absolute; + z-index: 16000; + left: 0; + top: 32px; + padding: 1em; + margin: -1em; + padding-top: 0; + margin-top: 0; + background-color: rgba(0,0,0,0.5); + -moz-border-radius: 0 0 1em 1em; + -webkit-border-bottom-right-radius: 1em; + -webkit-border-bottom-left-radius: 1em; +} + +div.tab-group span.tab div.submenu ul { + display: block; + margin: 0; + padding: 0; + list-style-type: none; + background: ${masthead_bg}; +} + +div.tab-group span.tab div.submenu ul li { + display: block; + padding: 0 1em; + white-space: nowrap; +} diff --git a/static/june_2007_style/reset.css.tmpl b/static/june_2007_style/reset.css.tmpl index 0eb970e56bf..a13abe4b1c2 100644 --- a/static/june_2007_style/reset.css.tmpl +++ b/static/june_2007_style/reset.css.tmpl @@ -1,3 +1,5 @@ -/* Copyright (c) 2007, Yahoo! Inc. All rights reserved. */ -body {font:13px arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}table {font-size:inherit;font:100%;}select, input, textarea {font:99% arial,helvetica,clean,sans-serif;}pre, code {font:115% monospace;*font-size:100%;}body * {line-height:1.22em;} +/* Copyright (c) 2008, Yahoo! Inc. All rights reserved. */ +## yui fonts +body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;} +## yui reset... commented out??? /*body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}ol,ul {list-style:none;}caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;}*/ diff --git a/static/scripts/galaxy.panels.js b/static/scripts/galaxy.panels.js index efe2ccb0a2a..1729fe5a2b4 100644 --- a/static/scripts/galaxy.panels.js +++ b/static/scripts/galaxy.panels.js @@ -245,3 +245,35 @@ function make_popupmenu( button_element, options ) { }; $( button_element ).click( click ); }; + +// Tab management + + +$(function() { + $("span.tab").each( function() { + var submenu = $(this).children( "div.submenu" ); + if ( submenu.length > 0 ) { + if ( $.browser.msie ) { + // Vile IE iframe hack -- even IE7 needs this + submenu.prepend( "" ); + } + $(this).hoverIntent( function() { submenu.show(); }, function() { submenu.hide(); } ); + submenu.click( function() { submenu.hide(); } ); + } + }); +}); + +function user_changed( user_email, is_admin ) { + if ( user_email ) { + $(".loggedin-only").show(); + $(".loggedout-only").hide(); + $("#user-email").text( user_email ); + if ( is_admin ) { + $(".admin-only").show(); + } + } else { + $(".loggedin-only").hide(); + $(".loggedout-only").show(); + $(".admin-only").hide(); + } +} \ No newline at end of file diff --git a/static/scripts/jquery.hoverIntent.js b/static/scripts/jquery.hoverIntent.js index 43cb7850539..d7b32d9027c 100644 --- a/static/scripts/jquery.hoverIntent.js +++ b/static/scripts/jquery.hoverIntent.js @@ -109,4 +109,4 @@ // bind the function to the two event listeners return this.mouseover(handleHover).mouseout(handleHover); }; -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/static/scripts/pack_scripts.py b/static/scripts/pack_scripts.py index 0bf5ac4e68b..a678cb84a29 100755 --- a/static/scripts/pack_scripts.py +++ b/static/scripts/pack_scripts.py @@ -10,7 +10,7 @@ from os import path # Scripts that should not be packed -- just copied do_not_pack = set( [ "ie_pngfix.js", "aflax.js" ] ) -cmd = "java -jar ../../scripts/yuicompressor.jar --type js -v %(fname)s -o packed/%(fname)s" +cmd = "java -jar ../../scripts/yuicompressor.jar --type js %(fname)s -o packed/%(fname)s" # If specific scripts specified on command line, just pack them, otherwise pack # all. diff --git a/static/scripts/packed/IE7.js b/static/scripts/packed/IE7.js index 4bd659cca95..a7d3ad9bd5e 100644 --- a/static/scripts/packed/IE7.js +++ b/static/scripts/packed/IE7.js @@ -1 +1 @@ -(function(){IE7={toString:function(){return"IE7 version 2.0 (beta3)"}};var appVersion=IE7.appVersion=navigator.appVersion.match(/MSIE (\d\.\d)/)[1];if(/ie7_off/.test(top.location.search)||appVersion<5){return }var Undefined=K();var quirksMode=document.compatMode!="CSS1Compat";var documentElement=document.documentElement,body,viewport;var ANON="!";var HEADER=":link{ie7-link:link}:visited{ie7-link:visited}";var RELATIVE=/^[\w\.]+[^:]*$/;function makePath(href,path){if(RELATIVE.test(href)){href=(path||"")+href}return href}function getPath(href,path){href=makePath(href,path);return href.slice(0,href.lastIndexOf("/")+1)}var script=document.scripts[document.scripts.length-1];var path=getPath(script.src);try{var httpRequest=new ActiveXObject("Microsoft.XMLHTTP")}catch(e){}var fileCache={};function loadFile(href,path){try{href=makePath(href,path);if(!fileCache[href]){httpRequest.open("GET",href,false);httpRequest.send();if(httpRequest.status==0||httpRequest.status==200){fileCache[href]=httpRequest.responseText}}}catch(e){}finally{return fileCache[href]||""}}if(appVersion<5.5){undefined=Undefined();ANON="HTML:!";var GLOBAL=/(g|gi)$/;var _String_replace=String.prototype.replace;String.prototype.replace=function(expression,replacement){if(typeof replacement=="function"){if(expression&&expression.constructor==RegExp){var regexp=expression;var global=regexp.global;if(global==null){global=GLOBAL.test(regexp)}if(global){regexp=new RegExp(regexp.source)}}else{regexp=new RegExp(rescape(expression))}var match,string=this,result="";while(string&&(match=regexp.exec(string))){result+=string.slice(0,match.index)+replacement.apply(this,match);string=string.slice(match.index+match[0].length);if(!global){break}}return result+string}return _String_replace.apply(this,arguments)};Array.prototype.pop=function(){if(this.length){var i=this[this.length-1];this.length--;return i}return undefined};Array.prototype.push=function(){for(var i=0;i":"","\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\/":"","@(namespace|import)[^;\\n]+[;\\n]":"","'(\\\\.|[^'\\\\])*'":encodeString,'"(\\\\.|[^"\\\\])*"':encodeString,"\\s+":" "});function encode(cssText){return encoder.exec(cssText)}function decode(cssText){return cssText.replace(ENCODED,function(match,index){return _strings[index-1]})}function encodeString(string){return"\x01"+_strings.push(string.replace(UNICODE,function(match,chr){return eval("'\\u"+"0000".slice(chr.length)+chr+"'")}).slice(1,-1).replace(QUOTES,"\\'"))}function getString(value){return STRING.test(value)?_strings[value.slice(1)-1]:value}var rotater=new RegGrp({Width:"Height",width:"height",Left:"Top",left:"top",Right:"Bottom",right:"bottom",onX:"onY"});function rotate(fn){return rotater.exec(fn)}var eventHandlers=[];function addResize(handler){addRecalc(handler);addEventHandler(window,"onresize",handler)}function addEventHandler(element,type,handler){element.attachEvent(type,handler);eventHandlers.push(arguments)}function removeEventHandler(element,type,handler){try{element.detachEvent(type,handler)}catch(ignore){}}addEventHandler(window,"onunload",function(){var handler;while(handler=eventHandlers.pop()){removeEventHandler(handler[0],handler[1],handler[2])}});function register(handler,element,condition){if(!handler.elements){handler.elements={}}if(condition){handler.elements[element.uniqueID]=element}else{delete handler.elements[element.uniqueID]}return condition}addEventHandler(window,"onbeforeprint",function(){if(!IE7.CSS.print){new StyleSheet("print")}IE7.CSS.print.recalc()});var PIXEL=/^\d+(px)?$/i;var PERCENT=/^\d+%$/;var getPixelValue=function(element,value){if(PIXEL.test(value)){return parseInt(value)}var style=element.style.left;var runtimeStyle=element.runtimeStyle.left;element.runtimeStyle.left=element.currentStyle.left;element.style.left=value||0;value=element.style.pixelLeft;element.style.left=style;element.runtimeStyle.left=runtimeStyle;return value};var $IE7="ie7-";var Fix=Base.extend({constructor:function(){this.fixes=[];this.recalcs=[]},init:Undefined});var recalcs=[];function addRecalc(recalc){recalcs.push(recalc)}IE7.recalc=function(){IE7.HTML.recalc();IE7.CSS.recalc();for(var i=0;i1?2:0;var block=cssParser.exec(selectors[i])||"if(0){";if(_wild){block+=format("if(e%1.nodeName!='!'){",_index)}var store=_duplicate>1?_TEST:"";block+=format(store+_STORE,_index);block+=Array(match(block,/\{/g).length+1).join("}");fn+=block}eval(format(_FN,reg)+cssParser.unescape(fn)+"return s?null:r}");_cache[selector]=_selectorFunction}return _cache[selector](context||document,single)}var _MSIE5=appVersion<6;var _EVALUATED=/^(href|src)$/;var _ATTRIBUTES={"class":"className","for":"htmlFor"};IE7._indexed=1;IE7._byId=function(document,id){var result=document.all[id]||null;if(!result||result.id==id){return result}for(var i=0;i+~,]|[^(]\+|^)([#.:\[])/g,IMPLIED_SPACE=/(^|,)([^\s>+~])/g,WHITESPACE=/\s*([\s>+~(),]|^|$)\s*/g,WILD_CARD=/\s\*\s/g;var CSSParser=RegGrp.extend({constructor:function(items){this.base(items);this.sorter=new RegGrp;this.sorter.add(/:not\([^)]*\)/,RegGrp.IGNORE);this.sorter.add(/([ >](\*|[\w-]+))([^: >+~]*)(:\w+-child(\([^)]+\))?)([^: >+~]*)/,"$1$3$6$4")},ignoreCase:true,escape:function(selector){return this.optimise(this.format(selector))},format:function(selector){return selector.replace(WHITESPACE,"$1").replace(IMPLIED_SPACE,"$1 $2").replace(IMPLIED_ASTERISK,"$1*$2")},optimise:function(selector){return this.sorter.exec(selector.replace(WILD_CARD,">* "))},unescape:function(selector){return decode(selector)}});var _OPERATORS={"":"%1!=null","=":"%1=='%2'","~=":/(^| )%1( |$)/,"|=":/^%1(-|$)/,"^=":/^%1/,"$=":/%1$/,"*=":/%1/};var _PSEUDO_CLASSES={"first-child":"!IE7._getPreviousElementSibling(e%1)",link:"e%1.currentStyle['ie7-link']=='link'",visited:"e%1.currentStyle['ie7-link']=='visited'"};var _VAR="var p%2=0,i%2,e%2,n%2=e%1.";var _ID="e%1.sourceIndex";var _TEST="var g="+_ID+";if(!p[g]){p[g]=1;";var _STORE="r[r.length]=e%1;if(s)return e%1;";var _FN="var _selectorFunction=function(e0,s){IE7._indexed++;var r=[],p={},reg=[%1],d=document;";var reg;var _index;var _wild;var _list;var _duplicate;var _cache={};var cssParser=new CSSParser({" (\\*|[\\w-]+)#([\\w-]+)":function(match,tagName,id){_wild=false;var replacement="var e%2=IE7._byId(d,'%4');if(e%2&&";if(tagName!="*"){replacement+="e%2.nodeName=='%3'&&"}replacement+="(e%1==d||e%1.contains(e%2))){";if(_list){replacement+=format("i%1=n%1.length;",_list)}return format(replacement,_index++,_index,tagName.toUpperCase(),id)}," (\\*|[\\w-]+)":function(match,tagName){_duplicate++;_wild=tagName=="*";var replacement=_VAR;replacement+=(_wild&&_MSIE5)?"all":"getElementsByTagName('%3')";replacement+=";for(i%2=0;(e%2=n%2[i%2]);i%2++){";return format(replacement,_index++,_list=_index,tagName.toUpperCase())},">(\\*|[\\w-]+)":function(match,tagName){var children=_list;_wild=tagName=="*";var replacement=_VAR;replacement+=children?"children":"childNodes";if(!_wild&&children){replacement+=".tags('%3')"}replacement+=";for(i%2=0;(e%2=n%2[i%2]);i%2++){";if(_wild){replacement+="if(e%2.nodeType==1){";_wild=_MSIE5}else{if(!children){replacement+="if(e%2.nodeName=='%3'){"}}return format(replacement,_index++,_list=_index,tagName.toUpperCase())},"\\+(\\*|[\\w-]+)":function(match,tagName){var replacement="";if(_wild){replacement+="if(e%1.nodeName!='!'){"}_wild=false;replacement+="e%1=IE7._getNextElementSibling(e%1);if(e%1";if(tagName!="*"){replacement+="&&e%1.nodeName=='%2'"}replacement+="){";return format(replacement,_index,tagName.toUpperCase())},"~(\\*|[\\w-]+)":function(match,tagName){var replacement="";if(_wild){replacement+="if(e%1.nodeName!='!'){"}_wild=false;_duplicate=2;replacement+="while(e%1=e%1.nextSibling){if(e%1.ie7_adjacent==IE7._indexed)break;if(";if(tagName=="*"){replacement+="e%1.nodeType==1";if(_MSIE5){replacement+="&&e%1.nodeName!='!'"}}else{replacement+="e%1.nodeName=='%2'"}replacement+="){e%1.ie7_adjacent=IE7._indexed;";return format(replacement,_index,tagName.toUpperCase())},"#([\\w-]+)":function(match,id){_wild=false;var replacement="if(e%1.id=='%2'){";if(_list){replacement+=format("i%1=n%1.length;",_list)}return format(replacement,_index,id)},"\\.([\\w-]+)":function(match,className){_wild=false;reg.push(new RegExp("(^|\\s)"+rescape(className)+"(\\s|$)"));return format("if(e%1.className&®[%2].test(e%1.className)){",_index,reg.length-1)},"\\[([\\w-]+)\\s*([^=]?=)?\\s*([^\\]]*)\\]":function(match,attr,operator,value){var alias=_ATTRIBUTES[attr]||attr;if(operator){var getAttribute="e%1.getAttribute('%2',2)";if(!_EVALUATED.test(attr)){getAttribute="e%1.%3||"+getAttribute}attr=format("("+getAttribute+")",_index,attr,alias)}else{attr=format("IE7._getAttribute(e%1,'%2')",_index,attr)}var replacement=_OPERATORS[operator||""]||"0";if(replacement&&replacement.source){reg.push(new RegExp(format(replacement.source,rescape(cssParser.unescape(value)))));replacement="reg[%2].test(%1)";value=reg.length-1}return"if("+format(replacement,attr,value)+"){"},":+([\\w-]+)(\\(([^)]+)\\))?":function(match,pseudoClass,$2,args){pseudoClass=_PSEUDO_CLASSES[pseudoClass];return"if("+(pseudoClass?format(pseudoClass,_index,args||""):"0")+"){"}});var HYPERLINK=/a(#[\w-]+)?(\.[\w-]+)?:(hover|active)/i;var BRACE1=/\s*\{\s*/,BRACE2=/\s*\}\s*/,COMMA=/\s*\,\s*/;var FIRST_LINE_LETTER=/(.*)(:first-(line|letter))/;var styleSheets=document.styleSheets;IE7.CSS=new (Fix.extend({parser:new Parser,screen:"",print:"",styles:[],rules:[],pseudoClasses:appVersion<7?"first\\-child":"",dynamicPseudoClasses:{toString:function(){var strings=[];for(var pseudoClass in this){strings.push(pseudoClass)}return strings.join("|")}},init:function(){var NONE="^\x01$";var CLASS="\\[class=?[^\\]]*\\]";var pseudoClasses=[];if(this.pseudoClasses){pseudoClasses.push(this.pseudoClasses)}var dynamicPseudoClasses=this.dynamicPseudoClasses.toString();if(dynamicPseudoClasses){pseudoClasses.push(dynamicPseudoClasses)}pseudoClasses=pseudoClasses.join("|");var unknown=appVersion<7?["[>+~[(]|([:.])\\w+\\1"]:[CLASS];if(pseudoClasses){unknown.push(":("+pseudoClasses+")")}this.UNKNOWN=new RegExp(unknown.join("|")||NONE,"i");var complex=appVersion<7?["\\[[^\\]]+\\]|[^\\s(\\[]+\\s*[+~]"]:[CLASS];var complexRule=complex.concat();if(pseudoClasses){complexRule.push(":("+pseudoClasses+")")}Rule.COMPLEX=new RegExp(complexRule.join("|")||NONE,"ig");if(this.pseudoClasses){complex.push(":("+this.pseudoClasses+")")}DynamicRule.COMPLEX=new RegExp(complex.join("|")||NONE,"i");DynamicRule.MATCH=new RegExp(dynamicPseudoClasses?"(.*):("+dynamicPseudoClasses+")(.*)":NONE,"i");this.createStyleSheet();this.refresh()},addEventHandler:function(){addEventHandler.apply(null,arguments)},addFix:function(expression,replacement){this.parser.add(expression,replacement)},addRecalc:function(propertyName,test,handler,replacement){test=new RegExp("([{;\\s])"+propertyName+"\\s*:\\s*"+test+"[^;}]*");var id=this.recalcs.length;if(replacement){replacement=propertyName+":"+replacement}this.addFix(test,function(match,$1){return(replacement?$1+replacement:match)+";ie7-"+match.slice(1)+";ie7_recalc"+id+":1"});this.recalcs.push(arguments);return id},apply:function(){this.getInlineStyles();new StyleSheet("screen");this.trash()},createStyleSheet:function(){this.styleSheet=document.createStyleSheet();this.styleSheet.ie7=true;this.styleSheet.owningElement.ie7=true;this.styleSheet.cssText=HEADER},getInlineStyles:function(){var styleSheets=document.getElementsByTagName("style"),styleSheet;for(var i=styleSheets.length-1;(styleSheet=styleSheets[i]);i--){if(!styleSheet.disabled&&!styleSheet.ie7){this.styles.push(styleSheet.innerHTML)}}},getText:function(styleSheet,path){try{var cssText=styleSheet.cssText}catch(e){cssText=""}if(httpRequest){cssText=loadFile(styleSheet.href,path)||cssText}return cssText},recalc:function(){this.screen.recalc();var RECALCS=/ie7_recalc\d+/g;var start=HEADER.match(/[{,]/g).length;var stop=start+(this.screen.cssText.match(/\{/g)||"").length;var rules=this.styleSheet.rules,rule;var calcs,calc,elements,element,i,j,k,id;for(i=start;i0&&Rule.CLASS.test(simple)){simple=simple.replace(Rule.CLASS,"");classes--}while(tags>0&&Rule.TAG.test(simple)){simple=simple.replace(Rule.TAG,"$1*");tags--}simple+="."+this.className;classes=Math.min(classes,2);tags=Math.min(tags,2);var score=-10*classes-tags;if(score>0){simple=simple+","+Rule.MAP[score]+" "+simple}return simple},remove:function(element){element.className=element.className.replace(this.MATCH,"$1")},toString:function(){return format("%1 {%2}",this.selectorText,this.cssText)}},{CHILD:/>/g,CLASS:/\.[\w-]+/,CLASSES:/[.:\[]/g,MULTI:/(\.[\w-]+)+/g,PREFIX:"ie7_class",TAG:/^\w+|([\s>+~])\w+/,TAGS:/^\w|[\s>+~]\w/g,MAP:{1:"html",2:"html body",10:".ie7_html",11:"html.ie7_html",12:"html.ie7_html body",20:".ie7_html .ie7_body",21:"html.ie7_html .ie7_body",22:"html.ie7_html body.ie7_body"}});var DynamicRule=Rule.extend({constructor:function(selector,attach,dynamicPseudoClass,target,cssText){this.attach=attach||"*";this.dynamicPseudoClass=IE7.CSS.dynamicPseudoClasses[dynamicPseudoClass];this.target=target;this.base(selector,cssText)},recalc:function(){var attaches=cssQuery(this.attach),attach;for(var i=0;attach=attaches[i];i++){var target=this.target?cssQuery(this.target,attach):[attach];if(target.length){this.dynamicPseudoClass.apply(attach,target,this)}}}});var DynamicPseudoClass=Base.extend({constructor:function(name,apply){this.name=name;this.apply=apply;this.instances={};IE7.CSS.dynamicPseudoClasses[name]=this},register:function(instance){var _class=instance[2];instance.id=_class.id+instance[0].uniqueID;if(!this.instances[instance.id]){var target=instance[1],j;for(j=0;j*:"+(type=="marginTop"?"first":"last")+"-child",element,true);if(child&&child.currentStyle.styleFloat=="none"&&IE7.hasLayout(child)){collapseMargin(child,type);margin=_getMargin(element,element.currentStyle[type]);childMargin=_getMargin(child,child.currentStyle[type]);if(margin<0||childMargin<0){element.runtimeStyle[type]=margin+childMargin}else{element.runtimeStyle[type]=Math.max(childMargin,margin)}child.runtimeStyle[type]="0px"}}}function _getMargin(element,value){return value=="auto"?0:getPixelValue(element,value)}var UNIT=/^[.\d][\w%]*$/,AUTO=/^(auto|0cm)$/;var applyWidth,applyHeight;IE7.Layout.borderBox=function(element){applyWidth(element);applyHeight(element)};var fixWidth=function(HEIGHT){applyWidth=function(element){if(!PERCENT.test(element.currentStyle.width)){fixWidth(element)}collapseMargins(element)};function fixWidth(element,value){if(!element.runtimeStyle.fixedWidth){if(!value){value=element.currentStyle.width}element.runtimeStyle.fixedWidth=(UNIT.test(value))?Math.max(0,getFixedWidth(element,value)):value;setOverrideStyle(element,"width",element.runtimeStyle.fixedWidth)}}function layoutWidth(element){if(!isFixed(element)){var layoutParent=element.offsetParent;while(layoutParent&&!IE7.hasLayout(layoutParent)){layoutParent=layoutParent.offsetParent}}return(layoutParent||viewport).clientWidth}function getPixelWidth(element,value){if(PERCENT.test(value)){return parseInt(parseFloat(value)/100*layoutWidth(element))}return getPixelValue(element,value)}var getFixedWidth=function(element,value){var borderBox=element.currentStyle["box-sizing"]=="border-box";var adjustment=0;if(quirksMode&&!borderBox){adjustment+=getBorderWidth(element)+getWidth(element,"padding")}else{if(!quirksMode&&borderBox){adjustment-=getBorderWidth(element)+getWidth(element,"padding")}}return getPixelWidth(element,value)+adjustment};function getBorderWidth(element){return element.offsetWidth-element.clientWidth}function getWidth(element,type){return getPixelWidth(element,element.currentStyle[type+"Left"])+getPixelWidth(element,element.currentStyle[type+"Right"])}HEADER+="*{minWidth:none;maxWidth:none;min-width:none;max-width:none}";layout.minWidth=function(element){if(element.currentStyle["min-width"]!=null){element.style.minWidth=element.currentStyle["min-width"]}if(register(arguments.callee,element,element.currentStyle.minWidth!="none")){layout.boxSizing(element);fixWidth(element);resizeWidth(element)}};eval("IE7.Layout.maxWidth="+String(layout.minWidth).replace(/min/g,"max"));function resizeWidth(element){var rect=element.getBoundingClientRect();var width=rect.right-rect.left;if(element.currentStyle.minWidth!="none"&&width<=getFixedWidth(element,element.currentStyle.minWidth)){element.runtimeStyle.width=element.currentStyle.minWidth}else{if(element.currentStyle.maxWidth!="none"&&width>=getFixedWidth(element,element.currentStyle.maxWidth)){element.runtimeStyle.width=element.currentStyle.maxWidth}else{element.runtimeStyle.width=element.runtimeStyle.fixedWidth}}}function fixRight(element){if(register(fixRight,element,/^(fixed|absolute)$/.test(element.currentStyle.position)&&getDefinedStyle(element,"left")!="auto"&&getDefinedStyle(element,"right")!="auto"&&AUTO.test(getDefinedStyle(element,"width")))){resizeRight(element);IE7.Layout.boxSizing(element)}}IE7.Layout.fixRight=fixRight;function resizeRight(element){var left=getPixelWidth(element,element.runtimeStyle._left||element.currentStyle.left);var width=layoutWidth(element)-getPixelWidth(element,element.currentStyle.right)-left-getWidth(element,"margin");if(parseInt(element.runtimeStyle.width)==width){return }element.runtimeStyle.width="";if(isFixed(element)||HEIGHT||element.offsetWidth=5.5&&appVersion<7){IE7.CSS.addFix(/background(-image)?\s*:\s*([^};]*)?url\(([^\)]+)\)([^;}]*)?/,function(match,$1,$2,url,$4){url=getString(url);return PNG.test(url)?"filter:"+format(PNG_FILTER,url,"crop")+";zoom:1;background"+($1||"")+":"+($2||"")+"none"+($4||""):match});IE7.HTML.addRecalc("img,input",function(element){if(element.tagName=="INPUT"&&element.type!="image"){return }fixImage(element);addEventHandler(element,"onpropertychange",function(){if(!printing&&event.propertyName=="src"&&element.src.indexOf(BLANK_GIF)==-1){fixImage(element)}})});var printing=false;addEventHandler(window,"onbeforeprint",function(){printing=true;for(var i=0;i=7){return }IE7.CSS.addRecalc("position","fixed",_positionFixed,"absolute");IE7.CSS.addRecalc("background(-attachment)?","[^};]*fixed",_backgroundFixed);var $viewport=quirksMode?"body":"documentElement";function _fixBackground(){if(body.currentStyle.backgroundAttachment!="fixed"){if(body.currentStyle.backgroundImage=="none"){body.runtimeStyle.backgroundRepeat="no-repeat";body.runtimeStyle.backgroundImage="url("+BLANK_GIF+")"}body.runtimeStyle.backgroundAttachment="fixed"}_fixBackground=Undefined}var _tmp=createTempElement("img");function _isFixed(element){return element?isFixed(element)||_isFixed(element.parentElement):false}function _setExpression(element,propertyName,expression){setTimeout("document.all."+element.uniqueID+".runtimeStyle.setExpression('"+propertyName+"','"+expression+"')",0)}function _backgroundFixed(element){if(register(_backgroundFixed,element,element.currentStyle.backgroundAttachment=="fixed"&&!element.contains(body))){_fixBackground();bgLeft(element);bgTop(element);_backgroundPosition(element)}}function _backgroundPosition(element){_tmp.src=element.currentStyle.backgroundImage.slice(5,-2);var parentElement=element.canHaveChildren?element:element.parentElement;parentElement.appendChild(_tmp);setOffsetLeft(element);setOffsetTop(element);parentElement.removeChild(_tmp)}function bgLeft(element){element.style.backgroundPositionX=element.currentStyle.backgroundPositionX;if(!_isFixed(element)){_setExpression(element,"backgroundPositionX","(parseInt(runtimeStyle.offsetLeft)+document."+$viewport+".scrollLeft)||0")}}eval(rotate(bgLeft));function setOffsetLeft(element){var propertyName=_isFixed(element)?"backgroundPositionX":"offsetLeft";element.runtimeStyle[propertyName]=getOffsetLeft(element,element.style.backgroundPositionX)-element.getBoundingClientRect().left-element.clientLeft+2}eval(rotate(setOffsetLeft));function getOffsetLeft(element,position){switch(position){case"left":case"top":return 0;case"right":case"bottom":return viewport.clientWidth-_tmp.offsetWidth;case"center":return(viewport.clientWidth-_tmp.offsetWidth)/2;default:if(PERCENT.test(position)){return parseInt((viewport.clientWidth-_tmp.offsetWidth)*parseFloat(position)/100)}_tmp.style.left=position;return _tmp.offsetLeft}}eval(rotate(getOffsetLeft));function _positionFixed(element){if(register(_positionFixed,element,isFixed(element))){setOverrideStyle(element,"position","absolute");setOverrideStyle(element,"left",element.currentStyle.left);setOverrideStyle(element,"top",element.currentStyle.top);_fixBackground();IE7.Layout.fixRight(element);_foregroundPosition(element)}}function _foregroundPosition(element,recalc){positionTop(element,recalc);positionLeft(element,recalc,true);if(!element.runtimeStyle.autoLeft&&element.currentStyle.marginLeft=="auto"&&element.currentStyle.right!="auto"){var left=viewport.clientWidth-getPixelWidth(element,element.currentStyle.right)-getPixelWidth(element,element.runtimeStyle._left)-element.clientWidth;if(element.currentStyle.marginRight=="auto"){left=parseInt(left/2)}if(_isFixed(element.offsetParent)){element.runtimeStyle.pixelLeft+=left}else{element.runtimeStyle.shiftLeft=left}}clipWidth(element);clipHeight(element)}function clipWidth(element){var fixWidth=element.runtimeStyle.fixWidth;element.runtimeStyle.borderRightWidth="";element.runtimeStyle.width=fixWidth?getPixelWidth(element,fixWidth):"";if(element.currentStyle.width!="auto"){var rect=element.getBoundingClientRect();var width=element.offsetWidth-viewport.clientWidth+rect.left-2;if(width>=0){element.runtimeStyle.borderRightWidth="0px";width=Math.max(getPixelValue(element,element.currentStyle.width)-width,0);setOverrideStyle(element,"width",width);return width}}}eval(rotate(clipWidth));function positionLeft(element,recalc){if(!recalc&&PERCENT.test(element.currentStyle.width)){element.runtimeStyle.fixWidth=element.currentStyle.width}if(element.runtimeStyle.fixWidth){element.runtimeStyle.width=getPixelWidth(element,element.runtimeStyle.fixWidth)}element.runtimeStyle.shiftLeft=0;element.runtimeStyle._left=element.currentStyle.left;element.runtimeStyle.autoLeft=element.currentStyle.right!="auto"&&element.currentStyle.left=="auto";element.runtimeStyle.left="";element.runtimeStyle.screenLeft=getScreenLeft(element);element.runtimeStyle.pixelLeft=element.runtimeStyle.screenLeft;if(!recalc&&!_isFixed(element.offsetParent)){_setExpression(element,"pixelLeft","runtimeStyle.screenLeft+runtimeStyle.shiftLeft+document."+$viewport+".scrollLeft")}}eval(rotate(positionLeft));function getScreenLeft(element){var screenLeft=element.offsetLeft,nested=1;if(element.runtimeStyle.autoLeft){screenLeft=viewport.clientWidth-element.offsetWidth-getPixelWidth(element,element.currentStyle.right)}if(element.currentStyle.marginLeft!="auto"){screenLeft-=getPixelWidth(element,element.currentStyle.marginLeft)}while(element=element.offsetParent){if(element.currentStyle.position!="static"){nested=-1}screenLeft+=element.offsetLeft*nested}return screenLeft}eval(rotate(getScreenLeft));function getPixelWidth(element,value){return PERCENT.test(value)?parseInt(parseFloat(value)/100*viewport.clientWidth):getPixelValue(element,value)}eval(rotate(getPixelWidth));function _resize(){var elements=_backgroundFixed.elements;for(var i in elements){_backgroundPosition(elements[i])}elements=_positionFixed.elements;for(i in elements){_foregroundPosition(elements[i],true);_foregroundPosition(elements[i],true)}_timer=0}var _timer;addResize(function(){if(!_timer){_timer=setTimeout(_resize,0)}})};var WRAPPER_STYLE={backgroundColor:"transparent",backgroundImage:"none",backgroundPositionX:null,backgroundPositionY:null,backgroundRepeat:null,borderTopWidth:0,borderRightWidth:0,borderBottomWidth:0,borderLeftStyle:"none",borderTopStyle:"none",borderRightStyle:"none",borderBottomStyle:"none",borderLeftWidth:0,height:null,marginTop:0,marginBottom:0,marginRight:0,marginLeft:0,width:"100%"};IE7.CSS.addRecalc("overflow","visible",function(element){if(element.parentNode.ie7_wrapped){return }if(IE7.Layout&&element.currentStyle["max-height"]!="auto"){IE7.Layout.maxHeight(element)}if(element.currentStyle.marginLeft=="auto"){element.style.marginLeft=0}if(element.currentStyle.marginRight=="auto"){element.style.marginRight=0}var wrapper=document.createElement(ANON);wrapper.ie7_wrapped=element;for(var propertyName in WRAPPER_STYLE){wrapper.style[propertyName]=element.currentStyle[propertyName];if(WRAPPER_STYLE[propertyName]!=null){element.runtimeStyle[propertyName]=WRAPPER_STYLE[propertyName]}}wrapper.style.display="block";wrapper.style.position="relative";element.runtimeStyle.position="absolute";element.parentNode.insertBefore(wrapper,element);wrapper.appendChild(element)});function ie7Quirks(){var FONT_SIZES="xx-small,x-small,small,medium,large,x-large,xx-large".split(",");for(var i=0;i":"","\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\/":"","@(namespace|import)[^;\\n]+[;\\n]":"","'(\\\\.|[^'\\\\])*'":encodeString,'"(\\\\.|[^"\\\\])*"':encodeString,"\\s+":" "});function encode(cssText){return encoder.exec(cssText)}function decode(cssText){return cssText.replace(ENCODED,function(match,index){return _strings[index-1]})}function encodeString(string){return"\x01"+_strings.push(string.replace(UNICODE,function(match,chr){return eval("'\\u"+"0000".slice(chr.length)+chr+"'")}).slice(1,-1).replace(QUOTES,"\\'"))}function getString(value){return STRING.test(value)?_strings[value.slice(1)-1]:value}var rotater=new RegGrp({Width:"Height",width:"height",Left:"Top",left:"top",Right:"Bottom",right:"bottom",onX:"onY"});function rotate(fn){return rotater.exec(fn)}var eventHandlers=[];function addResize(handler){addRecalc(handler);addEventHandler(window,"onresize",handler)}function addEventHandler(element,type,handler){element.attachEvent(type,handler);eventHandlers.push(arguments)}function removeEventHandler(element,type,handler){try{element.detachEvent(type,handler)}catch(ignore){}}addEventHandler(window,"onunload",function(){var handler;while(handler=eventHandlers.pop()){removeEventHandler(handler[0],handler[1],handler[2])}});function register(handler,element,condition){if(!handler.elements){handler.elements={}}if(condition){handler.elements[element.uniqueID]=element}else{delete handler.elements[element.uniqueID]}return condition}addEventHandler(window,"onbeforeprint",function(){if(!IE7.CSS.print){new StyleSheet("print")}IE7.CSS.print.recalc()});var PIXEL=/^\d+(px)?$/i;var PERCENT=/^\d+%$/;var getPixelValue=function(element,value){if(PIXEL.test(value)){return parseInt(value)}var style=element.style.left;var runtimeStyle=element.runtimeStyle.left;element.runtimeStyle.left=element.currentStyle.left;element.style.left=value||0;value=element.style.pixelLeft;element.style.left=style;element.runtimeStyle.left=runtimeStyle;return value};var $IE7="ie7-";var Fix=Base.extend({constructor:function(){this.fixes=[];this.recalcs=[]},init:Undefined});var recalcs=[];function addRecalc(recalc){recalcs.push(recalc)}IE7.recalc=function(){IE7.HTML.recalc();IE7.CSS.recalc();for(var i=0;i1?2:0;var block=cssParser.exec(selectors[i])||"if(0){";if(_wild){block+=format("if(e%1.nodeName!='!'){",_index)}var store=_duplicate>1?_TEST:"";block+=format(store+_STORE,_index);block+=Array(match(block,/\{/g).length+1).join("}");fn+=block}eval(format(_FN,reg)+cssParser.unescape(fn)+"return s?null:r}");_cache[selector]=_selectorFunction}return _cache[selector](context||document,single)}var _MSIE5=appVersion<6;var _EVALUATED=/^(href|src)$/;var _ATTRIBUTES={"class":"className","for":"htmlFor"};IE7._indexed=1;IE7._byId=function(document,id){var result=document.all[id]||null;if(!result||result.id==id){return result}for(var i=0;i+~,]|[^(]\+|^)([#.:\[])/g,IMPLIED_SPACE=/(^|,)([^\s>+~])/g,WHITESPACE=/\s*([\s>+~(),]|^|$)\s*/g,WILD_CARD=/\s\*\s/g;var CSSParser=RegGrp.extend({constructor:function(items){this.base(items);this.sorter=new RegGrp;this.sorter.add(/:not\([^)]*\)/,RegGrp.IGNORE);this.sorter.add(/([ >](\*|[\w-]+))([^: >+~]*)(:\w+-child(\([^)]+\))?)([^: >+~]*)/,"$1$3$6$4")},ignoreCase:true,escape:function(selector){return this.optimise(this.format(selector))},format:function(selector){return selector.replace(WHITESPACE,"$1").replace(IMPLIED_SPACE,"$1 $2").replace(IMPLIED_ASTERISK,"$1*$2")},optimise:function(selector){return this.sorter.exec(selector.replace(WILD_CARD,">* "))},unescape:function(selector){return decode(selector)}});var _OPERATORS={"":"%1!=null","=":"%1=='%2'","~=":/(^| )%1( |$)/,"|=":/^%1(-|$)/,"^=":/^%1/,"$=":/%1$/,"*=":/%1/};var _PSEUDO_CLASSES={"first-child":"!IE7._getPreviousElementSibling(e%1)",link:"e%1.currentStyle['ie7-link']=='link'",visited:"e%1.currentStyle['ie7-link']=='visited'"};var _VAR="var p%2=0,i%2,e%2,n%2=e%1.";var _ID="e%1.sourceIndex";var _TEST="var g="+_ID+";if(!p[g]){p[g]=1;";var _STORE="r[r.length]=e%1;if(s)return e%1;";var _FN="var _selectorFunction=function(e0,s){IE7._indexed++;var r=[],p={},reg=[%1],d=document;";var reg;var _index;var _wild;var _list;var _duplicate;var _cache={};var cssParser=new CSSParser({" (\\*|[\\w-]+)#([\\w-]+)":function(match,tagName,id){_wild=false;var replacement="var e%2=IE7._byId(d,'%4');if(e%2&&";if(tagName!="*"){replacement+="e%2.nodeName=='%3'&&"}replacement+="(e%1==d||e%1.contains(e%2))){";if(_list){replacement+=format("i%1=n%1.length;",_list)}return format(replacement,_index++,_index,tagName.toUpperCase(),id)}," (\\*|[\\w-]+)":function(match,tagName){_duplicate++;_wild=tagName=="*";var replacement=_VAR;replacement+=(_wild&&_MSIE5)?"all":"getElementsByTagName('%3')";replacement+=";for(i%2=0;(e%2=n%2[i%2]);i%2++){";return format(replacement,_index++,_list=_index,tagName.toUpperCase())},">(\\*|[\\w-]+)":function(match,tagName){var children=_list;_wild=tagName=="*";var replacement=_VAR;replacement+=children?"children":"childNodes";if(!_wild&&children){replacement+=".tags('%3')"}replacement+=";for(i%2=0;(e%2=n%2[i%2]);i%2++){";if(_wild){replacement+="if(e%2.nodeType==1){";_wild=_MSIE5}else{if(!children){replacement+="if(e%2.nodeName=='%3'){"}}return format(replacement,_index++,_list=_index,tagName.toUpperCase())},"\\+(\\*|[\\w-]+)":function(match,tagName){var replacement="";if(_wild){replacement+="if(e%1.nodeName!='!'){"}_wild=false;replacement+="e%1=IE7._getNextElementSibling(e%1);if(e%1";if(tagName!="*"){replacement+="&&e%1.nodeName=='%2'"}replacement+="){";return format(replacement,_index,tagName.toUpperCase())},"~(\\*|[\\w-]+)":function(match,tagName){var replacement="";if(_wild){replacement+="if(e%1.nodeName!='!'){"}_wild=false;_duplicate=2;replacement+="while(e%1=e%1.nextSibling){if(e%1.ie7_adjacent==IE7._indexed)break;if(";if(tagName=="*"){replacement+="e%1.nodeType==1";if(_MSIE5){replacement+="&&e%1.nodeName!='!'"}}else{replacement+="e%1.nodeName=='%2'"}replacement+="){e%1.ie7_adjacent=IE7._indexed;";return format(replacement,_index,tagName.toUpperCase())},"#([\\w-]+)":function(match,id){_wild=false;var replacement="if(e%1.id=='%2'){";if(_list){replacement+=format("i%1=n%1.length;",_list)}return format(replacement,_index,id)},"\\.([\\w-]+)":function(match,className){_wild=false;reg.push(new RegExp("(^|\\s)"+rescape(className)+"(\\s|$)"));return format("if(e%1.className&®[%2].test(e%1.className)){",_index,reg.length-1)},"\\[([\\w-]+)\\s*([^=]?=)?\\s*([^\\]]*)\\]":function(match,attr,operator,value){var alias=_ATTRIBUTES[attr]||attr;if(operator){var getAttribute="e%1.getAttribute('%2',2)";if(!_EVALUATED.test(attr)){getAttribute="e%1.%3||"+getAttribute}attr=format("("+getAttribute+")",_index,attr,alias)}else{attr=format("IE7._getAttribute(e%1,'%2')",_index,attr)}var replacement=_OPERATORS[operator||""]||"0";if(replacement&&replacement.source){reg.push(new RegExp(format(replacement.source,rescape(cssParser.unescape(value)))));replacement="reg[%2].test(%1)";value=reg.length-1}return"if("+format(replacement,attr,value)+"){"},":+([\\w-]+)(\\(([^)]+)\\))?":function(match,pseudoClass,$2,args){pseudoClass=_PSEUDO_CLASSES[pseudoClass];return"if("+(pseudoClass?format(pseudoClass,_index,args||""):"0")+"){"}});var HYPERLINK=/a(#[\w-]+)?(\.[\w-]+)?:(hover|active)/i;var BRACE1=/\s*\{\s*/,BRACE2=/\s*\}\s*/,COMMA=/\s*\,\s*/;var FIRST_LINE_LETTER=/(.*)(:first-(line|letter))/;var styleSheets=document.styleSheets;IE7.CSS=new (Fix.extend({parser:new Parser,screen:"",print:"",styles:[],rules:[],pseudoClasses:appVersion<7?"first\\-child":"",dynamicPseudoClasses:{toString:function(){var strings=[];for(var pseudoClass in this){strings.push(pseudoClass)}return strings.join("|")}},init:function(){var NONE="^\x01$";var CLASS="\\[class=?[^\\]]*\\]";var pseudoClasses=[];if(this.pseudoClasses){pseudoClasses.push(this.pseudoClasses)}var dynamicPseudoClasses=this.dynamicPseudoClasses.toString();if(dynamicPseudoClasses){pseudoClasses.push(dynamicPseudoClasses)}pseudoClasses=pseudoClasses.join("|");var unknown=appVersion<7?["[>+~[(]|([:.])\\w+\\1"]:[CLASS];if(pseudoClasses){unknown.push(":("+pseudoClasses+")")}this.UNKNOWN=new RegExp(unknown.join("|")||NONE,"i");var complex=appVersion<7?["\\[[^\\]]+\\]|[^\\s(\\[]+\\s*[+~]"]:[CLASS];var complexRule=complex.concat();if(pseudoClasses){complexRule.push(":("+pseudoClasses+")")}Rule.COMPLEX=new RegExp(complexRule.join("|")||NONE,"ig");if(this.pseudoClasses){complex.push(":("+this.pseudoClasses+")")}DynamicRule.COMPLEX=new RegExp(complex.join("|")||NONE,"i");DynamicRule.MATCH=new RegExp(dynamicPseudoClasses?"(.*):("+dynamicPseudoClasses+")(.*)":NONE,"i");this.createStyleSheet();this.refresh()},addEventHandler:function(){addEventHandler.apply(null,arguments)},addFix:function(expression,replacement){this.parser.add(expression,replacement)},addRecalc:function(propertyName,test,handler,replacement){test=new RegExp("([{;\\s])"+propertyName+"\\s*:\\s*"+test+"[^;}]*");var id=this.recalcs.length;if(replacement){replacement=propertyName+":"+replacement}this.addFix(test,function(match,$1){return(replacement?$1+replacement:match)+";ie7-"+match.slice(1)+";ie7_recalc"+id+":1"});this.recalcs.push(arguments);return id},apply:function(){this.getInlineStyles();new StyleSheet("screen");this.trash()},createStyleSheet:function(){this.styleSheet=document.createStyleSheet();this.styleSheet.ie7=true;this.styleSheet.owningElement.ie7=true;this.styleSheet.cssText=HEADER},getInlineStyles:function(){var styleSheets=document.getElementsByTagName("style"),styleSheet;for(var i=styleSheets.length-1;(styleSheet=styleSheets[i]);i--){if(!styleSheet.disabled&&!styleSheet.ie7){this.styles.push(styleSheet.innerHTML)}}},getText:function(styleSheet,path){try{var cssText=styleSheet.cssText}catch(e){cssText=""}if(httpRequest){cssText=loadFile(styleSheet.href,path)||cssText}return cssText},recalc:function(){this.screen.recalc();var RECALCS=/ie7_recalc\d+/g;var start=HEADER.match(/[{,]/g).length;var stop=start+(this.screen.cssText.match(/\{/g)||"").length;var rules=this.styleSheet.rules,rule;var calcs,calc,elements,element,i,j,k,id;for(i=start;i0&&Rule.CLASS.test(simple)){simple=simple.replace(Rule.CLASS,"");classes--}while(tags>0&&Rule.TAG.test(simple)){simple=simple.replace(Rule.TAG,"$1*");tags--}simple+="."+this.className;classes=Math.min(classes,2);tags=Math.min(tags,2);var score=-10*classes-tags;if(score>0){simple=simple+","+Rule.MAP[score]+" "+simple}return simple},remove:function(element){element.className=element.className.replace(this.MATCH,"$1")},toString:function(){return format("%1 {%2}",this.selectorText,this.cssText)}},{CHILD:/>/g,CLASS:/\.[\w-]+/,CLASSES:/[.:\[]/g,MULTI:/(\.[\w-]+)+/g,PREFIX:"ie7_class",TAG:/^\w+|([\s>+~])\w+/,TAGS:/^\w|[\s>+~]\w/g,MAP:{1:"html",2:"html body",10:".ie7_html",11:"html.ie7_html",12:"html.ie7_html body",20:".ie7_html .ie7_body",21:"html.ie7_html .ie7_body",22:"html.ie7_html body.ie7_body"}});var DynamicRule=Rule.extend({constructor:function(selector,attach,dynamicPseudoClass,target,cssText){this.attach=attach||"*";this.dynamicPseudoClass=IE7.CSS.dynamicPseudoClasses[dynamicPseudoClass];this.target=target;this.base(selector,cssText)},recalc:function(){var attaches=cssQuery(this.attach),attach;for(var i=0;attach=attaches[i];i++){var target=this.target?cssQuery(this.target,attach):[attach];if(target.length){this.dynamicPseudoClass.apply(attach,target,this)}}}});var DynamicPseudoClass=Base.extend({constructor:function(name,apply){this.name=name;this.apply=apply;this.instances={};IE7.CSS.dynamicPseudoClasses[name]=this},register:function(instance){var _class=instance[2];instance.id=_class.id+instance[0].uniqueID;if(!this.instances[instance.id]){var target=instance[1],j;for(j=0;j*:"+(type=="marginTop"?"first":"last")+"-child",element,true);if(child&&child.currentStyle.styleFloat=="none"&&IE7.hasLayout(child)){collapseMargin(child,type);margin=_getMargin(element,element.currentStyle[type]);childMargin=_getMargin(child,child.currentStyle[type]);if(margin<0||childMargin<0){element.runtimeStyle[type]=margin+childMargin}else{element.runtimeStyle[type]=Math.max(childMargin,margin)}child.runtimeStyle[type]="0px"}}}function _getMargin(element,value){return value=="auto"?0:getPixelValue(element,value)}var UNIT=/^[.\d][\w%]*$/,AUTO=/^(auto|0cm)$/;var applyWidth,applyHeight;IE7.Layout.borderBox=function(element){applyWidth(element);applyHeight(element)};var fixWidth=function(HEIGHT){applyWidth=function(element){if(!PERCENT.test(element.currentStyle.width)){fixWidth(element)}collapseMargins(element)};function fixWidth(element,value){if(!element.runtimeStyle.fixedWidth){if(!value){value=element.currentStyle.width}element.runtimeStyle.fixedWidth=(UNIT.test(value))?Math.max(0,getFixedWidth(element,value)):value;setOverrideStyle(element,"width",element.runtimeStyle.fixedWidth)}}function layoutWidth(element){if(!isFixed(element)){var layoutParent=element.offsetParent;while(layoutParent&&!IE7.hasLayout(layoutParent)){layoutParent=layoutParent.offsetParent}}return(layoutParent||viewport).clientWidth}function getPixelWidth(element,value){if(PERCENT.test(value)){return parseInt(parseFloat(value)/100*layoutWidth(element))}return getPixelValue(element,value)}var getFixedWidth=function(element,value){var borderBox=element.currentStyle["box-sizing"]=="border-box";var adjustment=0;if(quirksMode&&!borderBox){adjustment+=getBorderWidth(element)+getWidth(element,"padding")}else{if(!quirksMode&&borderBox){adjustment-=getBorderWidth(element)+getWidth(element,"padding")}}return getPixelWidth(element,value)+adjustment};function getBorderWidth(element){return element.offsetWidth-element.clientWidth}function getWidth(element,type){return getPixelWidth(element,element.currentStyle[type+"Left"])+getPixelWidth(element,element.currentStyle[type+"Right"])}HEADER+="*{minWidth:none;maxWidth:none;min-width:none;max-width:none}";layout.minWidth=function(element){if(element.currentStyle["min-width"]!=null){element.style.minWidth=element.currentStyle["min-width"]}if(register(arguments.callee,element,element.currentStyle.minWidth!="none")){layout.boxSizing(element);fixWidth(element);resizeWidth(element)}};eval("IE7.Layout.maxWidth="+String(layout.minWidth).replace(/min/g,"max"));function resizeWidth(element){var rect=element.getBoundingClientRect();var width=rect.right-rect.left;if(element.currentStyle.minWidth!="none"&&width<=getFixedWidth(element,element.currentStyle.minWidth)){element.runtimeStyle.width=element.currentStyle.minWidth}else{if(element.currentStyle.maxWidth!="none"&&width>=getFixedWidth(element,element.currentStyle.maxWidth)){element.runtimeStyle.width=element.currentStyle.maxWidth}else{element.runtimeStyle.width=element.runtimeStyle.fixedWidth}}}function fixRight(element){if(register(fixRight,element,/^(fixed|absolute)$/.test(element.currentStyle.position)&&getDefinedStyle(element,"left")!="auto"&&getDefinedStyle(element,"right")!="auto"&&AUTO.test(getDefinedStyle(element,"width")))){resizeRight(element);IE7.Layout.boxSizing(element)}}IE7.Layout.fixRight=fixRight;function resizeRight(element){var left=getPixelWidth(element,element.runtimeStyle._left||element.currentStyle.left);var width=layoutWidth(element)-getPixelWidth(element,element.currentStyle.right)-left-getWidth(element,"margin");if(parseInt(element.runtimeStyle.width)==width){return}element.runtimeStyle.width="";if(isFixed(element)||HEIGHT||element.offsetWidth=5.5&&appVersion<7){IE7.CSS.addFix(/background(-image)?\s*:\s*([^};]*)?url\(([^\)]+)\)([^;}]*)?/,function(match,$1,$2,url,$4){url=getString(url);return PNG.test(url)?"filter:"+format(PNG_FILTER,url,"crop")+";zoom:1;background"+($1||"")+":"+($2||"")+"none"+($4||""):match});IE7.HTML.addRecalc("img,input",function(element){if(element.tagName=="INPUT"&&element.type!="image"){return}fixImage(element);addEventHandler(element,"onpropertychange",function(){if(!printing&&event.propertyName=="src"&&element.src.indexOf(BLANK_GIF)==-1){fixImage(element)}})});var printing=false;addEventHandler(window,"onbeforeprint",function(){printing=true;for(var i=0;i=7){return}IE7.CSS.addRecalc("position","fixed",_positionFixed,"absolute");IE7.CSS.addRecalc("background(-attachment)?","[^};]*fixed",_backgroundFixed);var $viewport=quirksMode?"body":"documentElement";function _fixBackground(){if(body.currentStyle.backgroundAttachment!="fixed"){if(body.currentStyle.backgroundImage=="none"){body.runtimeStyle.backgroundRepeat="no-repeat";body.runtimeStyle.backgroundImage="url("+BLANK_GIF+")"}body.runtimeStyle.backgroundAttachment="fixed"}_fixBackground=Undefined}var _tmp=createTempElement("img");function _isFixed(element){return element?isFixed(element)||_isFixed(element.parentElement):false}function _setExpression(element,propertyName,expression){setTimeout("document.all."+element.uniqueID+".runtimeStyle.setExpression('"+propertyName+"','"+expression+"')",0)}function _backgroundFixed(element){if(register(_backgroundFixed,element,element.currentStyle.backgroundAttachment=="fixed"&&!element.contains(body))){_fixBackground();bgLeft(element);bgTop(element);_backgroundPosition(element)}}function _backgroundPosition(element){_tmp.src=element.currentStyle.backgroundImage.slice(5,-2);var parentElement=element.canHaveChildren?element:element.parentElement;parentElement.appendChild(_tmp);setOffsetLeft(element);setOffsetTop(element);parentElement.removeChild(_tmp)}function bgLeft(element){element.style.backgroundPositionX=element.currentStyle.backgroundPositionX;if(!_isFixed(element)){_setExpression(element,"backgroundPositionX","(parseInt(runtimeStyle.offsetLeft)+document."+$viewport+".scrollLeft)||0")}}eval(rotate(bgLeft));function setOffsetLeft(element){var propertyName=_isFixed(element)?"backgroundPositionX":"offsetLeft";element.runtimeStyle[propertyName]=getOffsetLeft(element,element.style.backgroundPositionX)-element.getBoundingClientRect().left-element.clientLeft+2}eval(rotate(setOffsetLeft));function getOffsetLeft(element,position){switch(position){case"left":case"top":return 0;case"right":case"bottom":return viewport.clientWidth-_tmp.offsetWidth;case"center":return(viewport.clientWidth-_tmp.offsetWidth)/2;default:if(PERCENT.test(position)){return parseInt((viewport.clientWidth-_tmp.offsetWidth)*parseFloat(position)/100)}_tmp.style.left=position;return _tmp.offsetLeft}}eval(rotate(getOffsetLeft));function _positionFixed(element){if(register(_positionFixed,element,isFixed(element))){setOverrideStyle(element,"position","absolute");setOverrideStyle(element,"left",element.currentStyle.left);setOverrideStyle(element,"top",element.currentStyle.top);_fixBackground();IE7.Layout.fixRight(element);_foregroundPosition(element)}}function _foregroundPosition(element,recalc){positionTop(element,recalc);positionLeft(element,recalc,true);if(!element.runtimeStyle.autoLeft&&element.currentStyle.marginLeft=="auto"&&element.currentStyle.right!="auto"){var left=viewport.clientWidth-getPixelWidth(element,element.currentStyle.right)-getPixelWidth(element,element.runtimeStyle._left)-element.clientWidth;if(element.currentStyle.marginRight=="auto"){left=parseInt(left/2)}if(_isFixed(element.offsetParent)){element.runtimeStyle.pixelLeft+=left}else{element.runtimeStyle.shiftLeft=left}}clipWidth(element);clipHeight(element)}function clipWidth(element){var fixWidth=element.runtimeStyle.fixWidth;element.runtimeStyle.borderRightWidth="";element.runtimeStyle.width=fixWidth?getPixelWidth(element,fixWidth):"";if(element.currentStyle.width!="auto"){var rect=element.getBoundingClientRect();var width=element.offsetWidth-viewport.clientWidth+rect.left-2;if(width>=0){element.runtimeStyle.borderRightWidth="0px";width=Math.max(getPixelValue(element,element.currentStyle.width)-width,0);setOverrideStyle(element,"width",width);return width}}}eval(rotate(clipWidth));function positionLeft(element,recalc){if(!recalc&&PERCENT.test(element.currentStyle.width)){element.runtimeStyle.fixWidth=element.currentStyle.width}if(element.runtimeStyle.fixWidth){element.runtimeStyle.width=getPixelWidth(element,element.runtimeStyle.fixWidth)}element.runtimeStyle.shiftLeft=0;element.runtimeStyle._left=element.currentStyle.left;element.runtimeStyle.autoLeft=element.currentStyle.right!="auto"&&element.currentStyle.left=="auto";element.runtimeStyle.left="";element.runtimeStyle.screenLeft=getScreenLeft(element);element.runtimeStyle.pixelLeft=element.runtimeStyle.screenLeft;if(!recalc&&!_isFixed(element.offsetParent)){_setExpression(element,"pixelLeft","runtimeStyle.screenLeft+runtimeStyle.shiftLeft+document."+$viewport+".scrollLeft")}}eval(rotate(positionLeft));function getScreenLeft(element){var screenLeft=element.offsetLeft,nested=1;if(element.runtimeStyle.autoLeft){screenLeft=viewport.clientWidth-element.offsetWidth-getPixelWidth(element,element.currentStyle.right)}if(element.currentStyle.marginLeft!="auto"){screenLeft-=getPixelWidth(element,element.currentStyle.marginLeft)}while(element=element.offsetParent){if(element.currentStyle.position!="static"){nested=-1}screenLeft+=element.offsetLeft*nested}return screenLeft}eval(rotate(getScreenLeft));function getPixelWidth(element,value){return PERCENT.test(value)?parseInt(parseFloat(value)/100*viewport.clientWidth):getPixelValue(element,value)}eval(rotate(getPixelWidth));function _resize(){var elements=_backgroundFixed.elements;for(var i in elements){_backgroundPosition(elements[i])}elements=_positionFixed.elements;for(i in elements){_foregroundPosition(elements[i],true);_foregroundPosition(elements[i],true)}_timer=0}var _timer;addResize(function(){if(!_timer){_timer=setTimeout(_resize,0)}})};var WRAPPER_STYLE={backgroundColor:"transparent",backgroundImage:"none",backgroundPositionX:null,backgroundPositionY:null,backgroundRepeat:null,borderTopWidth:0,borderRightWidth:0,borderBottomWidth:0,borderLeftStyle:"none",borderTopStyle:"none",borderRightStyle:"none",borderBottomStyle:"none",borderLeftWidth:0,height:null,marginTop:0,marginBottom:0,marginRight:0,marginLeft:0,width:"100%"};IE7.CSS.addRecalc("overflow","visible",function(element){if(element.parentNode.ie7_wrapped){return}if(IE7.Layout&&element.currentStyle["max-height"]!="auto"){IE7.Layout.maxHeight(element)}if(element.currentStyle.marginLeft=="auto"){element.style.marginLeft=0}if(element.currentStyle.marginRight=="auto"){element.style.marginRight=0}var wrapper=document.createElement(ANON);wrapper.ie7_wrapped=element;for(var propertyName in WRAPPER_STYLE){wrapper.style[propertyName]=element.currentStyle[propertyName];if(WRAPPER_STYLE[propertyName]!=null){element.runtimeStyle[propertyName]=WRAPPER_STYLE[propertyName]}}wrapper.style.display="block";wrapper.style.position="relative";element.runtimeStyle.position="absolute";element.parentNode.insertBefore(wrapper,element);wrapper.appendChild(element)});function ie7Quirks(){var FONT_SIZES="xx-small,x-small,small,medium,large,x-large,xx-large".split(",");for(var i=0;i":"","\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\/":"","@(namespace|import)[^;\\n]+[;\\n]":"","'(\\\\.|[^'\\\\])*'":encodeString,'"(\\\\.|[^"\\\\])*"':encodeString,"\\s+":" "});function encode(cssText){return encoder.exec(cssText)}function decode(cssText){return cssText.replace(ENCODED,function(match,index){return _strings[index-1]})}function encodeString(string){return"\x01"+_strings.push(string.replace(UNICODE,function(match,chr){return eval("'\\u"+"0000".slice(chr.length)+chr+"'")}).slice(1,-1).replace(QUOTES,"\\'"))}function getString(value){return STRING.test(value)?_strings[value.slice(1)-1]:value}var rotater=new RegGrp({Width:"Height",width:"height",Left:"Top",left:"top",Right:"Bottom",right:"bottom",onX:"onY"});function rotate(fn){return rotater.exec(fn)}var eventHandlers=[];function addResize(handler){addRecalc(handler);addEventHandler(window,"onresize",handler)}function addEventHandler(element,type,handler){element.attachEvent(type,handler);eventHandlers.push(arguments)}function removeEventHandler(element,type,handler){try{element.detachEvent(type,handler)}catch(ignore){}}addEventHandler(window,"onunload",function(){var handler;while(handler=eventHandlers.pop()){removeEventHandler(handler[0],handler[1],handler[2])}});function register(handler,element,condition){if(!handler.elements){handler.elements={}}if(condition){handler.elements[element.uniqueID]=element}else{delete handler.elements[element.uniqueID]}return condition}addEventHandler(window,"onbeforeprint",function(){if(!IE7.CSS.print){new StyleSheet("print")}IE7.CSS.print.recalc()});var PIXEL=/^\d+(px)?$/i;var PERCENT=/^\d+%$/;var getPixelValue=function(element,value){if(PIXEL.test(value)){return parseInt(value)}var style=element.style.left;var runtimeStyle=element.runtimeStyle.left;element.runtimeStyle.left=element.currentStyle.left;element.style.left=value||0;value=element.style.pixelLeft;element.style.left=style;element.runtimeStyle.left=runtimeStyle;return value};var $IE7="ie7-";var Fix=Base.extend({constructor:function(){this.fixes=[];this.recalcs=[]},init:Undefined});var recalcs=[];function addRecalc(recalc){recalcs.push(recalc)}IE7.recalc=function(){IE7.HTML.recalc();IE7.CSS.recalc();for(var i=0;i1?2:0;var block=cssParser.exec(selectors[i])||"if(0){";if(_wild){block+=format("if(e%1.nodeName!='!'){",_index)}var store=_duplicate>1?_TEST:"";block+=format(store+_STORE,_index);block+=Array(match(block,/\{/g).length+1).join("}");fn+=block}eval(format(_FN,reg)+cssParser.unescape(fn)+"return s?null:r}");_cache[selector]=_selectorFunction}return _cache[selector](context||document,single)}var _MSIE5=appVersion<6;var _EVALUATED=/^(href|src)$/;var _ATTRIBUTES={"class":"className","for":"htmlFor"};IE7._indexed=1;IE7._byId=function(document,id){var result=document.all[id]||null;if(!result||result.id==id){return result}for(var i=0;i+~,]|[^(]\+|^)([#.:\[])/g,IMPLIED_SPACE=/(^|,)([^\s>+~])/g,WHITESPACE=/\s*([\s>+~(),]|^|$)\s*/g,WILD_CARD=/\s\*\s/g;var CSSParser=RegGrp.extend({constructor:function(items){this.base(items);this.sorter=new RegGrp;this.sorter.add(/:not\([^)]*\)/,RegGrp.IGNORE);this.sorter.add(/([ >](\*|[\w-]+))([^: >+~]*)(:\w+-child(\([^)]+\))?)([^: >+~]*)/,"$1$3$6$4")},ignoreCase:true,escape:function(selector){return this.optimise(this.format(selector))},format:function(selector){return selector.replace(WHITESPACE,"$1").replace(IMPLIED_SPACE,"$1 $2").replace(IMPLIED_ASTERISK,"$1*$2")},optimise:function(selector){return this.sorter.exec(selector.replace(WILD_CARD,">* "))},unescape:function(selector){return decode(selector)}});var _OPERATORS={"":"%1!=null","=":"%1=='%2'","~=":/(^| )%1( |$)/,"|=":/^%1(-|$)/,"^=":/^%1/,"$=":/%1$/,"*=":/%1/};var _PSEUDO_CLASSES={"first-child":"!IE7._getPreviousElementSibling(e%1)",link:"e%1.currentStyle['ie7-link']=='link'",visited:"e%1.currentStyle['ie7-link']=='visited'"};var _VAR="var p%2=0,i%2,e%2,n%2=e%1.";var _ID="e%1.sourceIndex";var _TEST="var g="+_ID+";if(!p[g]){p[g]=1;";var _STORE="r[r.length]=e%1;if(s)return e%1;";var _FN="var _selectorFunction=function(e0,s){IE7._indexed++;var r=[],p={},reg=[%1],d=document;";var reg;var _index;var _wild;var _list;var _duplicate;var _cache={};var cssParser=new CSSParser({" (\\*|[\\w-]+)#([\\w-]+)":function(match,tagName,id){_wild=false;var replacement="var e%2=IE7._byId(d,'%4');if(e%2&&";if(tagName!="*"){replacement+="e%2.nodeName=='%3'&&"}replacement+="(e%1==d||e%1.contains(e%2))){";if(_list){replacement+=format("i%1=n%1.length;",_list)}return format(replacement,_index++,_index,tagName.toUpperCase(),id)}," (\\*|[\\w-]+)":function(match,tagName){_duplicate++;_wild=tagName=="*";var replacement=_VAR;replacement+=(_wild&&_MSIE5)?"all":"getElementsByTagName('%3')";replacement+=";for(i%2=0;(e%2=n%2[i%2]);i%2++){";return format(replacement,_index++,_list=_index,tagName.toUpperCase())},">(\\*|[\\w-]+)":function(match,tagName){var children=_list;_wild=tagName=="*";var replacement=_VAR;replacement+=children?"children":"childNodes";if(!_wild&&children){replacement+=".tags('%3')"}replacement+=";for(i%2=0;(e%2=n%2[i%2]);i%2++){";if(_wild){replacement+="if(e%2.nodeType==1){";_wild=_MSIE5}else{if(!children){replacement+="if(e%2.nodeName=='%3'){"}}return format(replacement,_index++,_list=_index,tagName.toUpperCase())},"\\+(\\*|[\\w-]+)":function(match,tagName){var replacement="";if(_wild){replacement+="if(e%1.nodeName!='!'){"}_wild=false;replacement+="e%1=IE7._getNextElementSibling(e%1);if(e%1";if(tagName!="*"){replacement+="&&e%1.nodeName=='%2'"}replacement+="){";return format(replacement,_index,tagName.toUpperCase())},"~(\\*|[\\w-]+)":function(match,tagName){var replacement="";if(_wild){replacement+="if(e%1.nodeName!='!'){"}_wild=false;_duplicate=2;replacement+="while(e%1=e%1.nextSibling){if(e%1.ie7_adjacent==IE7._indexed)break;if(";if(tagName=="*"){replacement+="e%1.nodeType==1";if(_MSIE5){replacement+="&&e%1.nodeName!='!'"}}else{replacement+="e%1.nodeName=='%2'"}replacement+="){e%1.ie7_adjacent=IE7._indexed;";return format(replacement,_index,tagName.toUpperCase())},"#([\\w-]+)":function(match,id){_wild=false;var replacement="if(e%1.id=='%2'){";if(_list){replacement+=format("i%1=n%1.length;",_list)}return format(replacement,_index,id)},"\\.([\\w-]+)":function(match,className){_wild=false;reg.push(new RegExp("(^|\\s)"+rescape(className)+"(\\s|$)"));return format("if(e%1.className&®[%2].test(e%1.className)){",_index,reg.length-1)},"\\[([\\w-]+)\\s*([^=]?=)?\\s*([^\\]]*)\\]":function(match,attr,operator,value){var alias=_ATTRIBUTES[attr]||attr;if(operator){var getAttribute="e%1.getAttribute('%2',2)";if(!_EVALUATED.test(attr)){getAttribute="e%1.%3||"+getAttribute}attr=format("("+getAttribute+")",_index,attr,alias)}else{attr=format("IE7._getAttribute(e%1,'%2')",_index,attr)}var replacement=_OPERATORS[operator||""]||"0";if(replacement&&replacement.source){reg.push(new RegExp(format(replacement.source,rescape(cssParser.unescape(value)))));replacement="reg[%2].test(%1)";value=reg.length-1}return"if("+format(replacement,attr,value)+"){"},":+([\\w-]+)(\\(([^)]+)\\))?":function(match,pseudoClass,$2,args){pseudoClass=_PSEUDO_CLASSES[pseudoClass];return"if("+(pseudoClass?format(pseudoClass,_index,args||""):"0")+"){"}});var HYPERLINK=/a(#[\w-]+)?(\.[\w-]+)?:(hover|active)/i;var BRACE1=/\s*\{\s*/,BRACE2=/\s*\}\s*/,COMMA=/\s*\,\s*/;var FIRST_LINE_LETTER=/(.*)(:first-(line|letter))/;var styleSheets=document.styleSheets;IE7.CSS=new (Fix.extend({parser:new Parser,screen:"",print:"",styles:[],rules:[],pseudoClasses:appVersion<7?"first\\-child":"",dynamicPseudoClasses:{toString:function(){var strings=[];for(var pseudoClass in this){strings.push(pseudoClass)}return strings.join("|")}},init:function(){var NONE="^\x01$";var CLASS="\\[class=?[^\\]]*\\]";var pseudoClasses=[];if(this.pseudoClasses){pseudoClasses.push(this.pseudoClasses)}var dynamicPseudoClasses=this.dynamicPseudoClasses.toString();if(dynamicPseudoClasses){pseudoClasses.push(dynamicPseudoClasses)}pseudoClasses=pseudoClasses.join("|");var unknown=appVersion<7?["[>+~[(]|([:.])\\w+\\1"]:[CLASS];if(pseudoClasses){unknown.push(":("+pseudoClasses+")")}this.UNKNOWN=new RegExp(unknown.join("|")||NONE,"i");var complex=appVersion<7?["\\[[^\\]]+\\]|[^\\s(\\[]+\\s*[+~]"]:[CLASS];var complexRule=complex.concat();if(pseudoClasses){complexRule.push(":("+pseudoClasses+")")}Rule.COMPLEX=new RegExp(complexRule.join("|")||NONE,"ig");if(this.pseudoClasses){complex.push(":("+this.pseudoClasses+")")}DynamicRule.COMPLEX=new RegExp(complex.join("|")||NONE,"i");DynamicRule.MATCH=new RegExp(dynamicPseudoClasses?"(.*):("+dynamicPseudoClasses+")(.*)":NONE,"i");this.createStyleSheet();this.refresh()},addEventHandler:function(){addEventHandler.apply(null,arguments)},addFix:function(expression,replacement){this.parser.add(expression,replacement)},addRecalc:function(propertyName,test,handler,replacement){test=new RegExp("([{;\\s])"+propertyName+"\\s*:\\s*"+test+"[^;}]*");var id=this.recalcs.length;if(replacement){replacement=propertyName+":"+replacement}this.addFix(test,function(match,$1){return(replacement?$1+replacement:match)+";ie7-"+match.slice(1)+";ie7_recalc"+id+":1"});this.recalcs.push(arguments);return id},apply:function(){this.getInlineStyles();new StyleSheet("screen");this.trash()},createStyleSheet:function(){this.styleSheet=document.createStyleSheet();this.styleSheet.ie7=true;this.styleSheet.owningElement.ie7=true;this.styleSheet.cssText=HEADER},getInlineStyles:function(){var styleSheets=document.getElementsByTagName("style"),styleSheet;for(var i=styleSheets.length-1;(styleSheet=styleSheets[i]);i--){if(!styleSheet.disabled&&!styleSheet.ie7){this.styles.push(styleSheet.innerHTML)}}},getText:function(styleSheet,path){try{var cssText=styleSheet.cssText}catch(e){cssText=""}if(httpRequest){cssText=loadFile(styleSheet.href,path)||cssText}return cssText},recalc:function(){this.screen.recalc();var RECALCS=/ie7_recalc\d+/g;var start=HEADER.match(/[{,]/g).length;var stop=start+(this.screen.cssText.match(/\{/g)||"").length;var rules=this.styleSheet.rules,rule;var calcs,calc,elements,element,i,j,k,id;for(i=start;i0&&Rule.CLASS.test(simple)){simple=simple.replace(Rule.CLASS,"");classes--}while(tags>0&&Rule.TAG.test(simple)){simple=simple.replace(Rule.TAG,"$1*");tags--}simple+="."+this.className;classes=Math.min(classes,2);tags=Math.min(tags,2);var score=-10*classes-tags;if(score>0){simple=simple+","+Rule.MAP[score]+" "+simple}return simple},remove:function(element){element.className=element.className.replace(this.MATCH,"$1")},toString:function(){return format("%1 {%2}",this.selectorText,this.cssText)}},{CHILD:/>/g,CLASS:/\.[\w-]+/,CLASSES:/[.:\[]/g,MULTI:/(\.[\w-]+)+/g,PREFIX:"ie7_class",TAG:/^\w+|([\s>+~])\w+/,TAGS:/^\w|[\s>+~]\w/g,MAP:{1:"html",2:"html body",10:".ie7_html",11:"html.ie7_html",12:"html.ie7_html body",20:".ie7_html .ie7_body",21:"html.ie7_html .ie7_body",22:"html.ie7_html body.ie7_body"}});var DynamicRule=Rule.extend({constructor:function(selector,attach,dynamicPseudoClass,target,cssText){this.attach=attach||"*";this.dynamicPseudoClass=IE7.CSS.dynamicPseudoClasses[dynamicPseudoClass];this.target=target;this.base(selector,cssText)},recalc:function(){var attaches=cssQuery(this.attach),attach;for(var i=0;attach=attaches[i];i++){var target=this.target?cssQuery(this.target,attach):[attach];if(target.length){this.dynamicPseudoClass.apply(attach,target,this)}}}});var DynamicPseudoClass=Base.extend({constructor:function(name,apply){this.name=name;this.apply=apply;this.instances={};IE7.CSS.dynamicPseudoClasses[name]=this},register:function(instance){var _class=instance[2];instance.id=_class.id+instance[0].uniqueID;if(!this.instances[instance.id]){var target=instance[1],j;for(j=0;j*:"+(type=="marginTop"?"first":"last")+"-child",element,true);if(child&&child.currentStyle.styleFloat=="none"&&IE7.hasLayout(child)){collapseMargin(child,type);margin=_getMargin(element,element.currentStyle[type]);childMargin=_getMargin(child,child.currentStyle[type]);if(margin<0||childMargin<0){element.runtimeStyle[type]=margin+childMargin}else{element.runtimeStyle[type]=Math.max(childMargin,margin)}child.runtimeStyle[type]="0px"}}}function _getMargin(element,value){return value=="auto"?0:getPixelValue(element,value)}var UNIT=/^[.\d][\w%]*$/,AUTO=/^(auto|0cm)$/;var applyWidth,applyHeight;IE7.Layout.borderBox=function(element){applyWidth(element);applyHeight(element)};var fixWidth=function(HEIGHT){applyWidth=function(element){if(!PERCENT.test(element.currentStyle.width)){fixWidth(element)}collapseMargins(element)};function fixWidth(element,value){if(!element.runtimeStyle.fixedWidth){if(!value){value=element.currentStyle.width}element.runtimeStyle.fixedWidth=(UNIT.test(value))?Math.max(0,getFixedWidth(element,value)):value;setOverrideStyle(element,"width",element.runtimeStyle.fixedWidth)}}function layoutWidth(element){if(!isFixed(element)){var layoutParent=element.offsetParent;while(layoutParent&&!IE7.hasLayout(layoutParent)){layoutParent=layoutParent.offsetParent}}return(layoutParent||viewport).clientWidth}function getPixelWidth(element,value){if(PERCENT.test(value)){return parseInt(parseFloat(value)/100*layoutWidth(element))}return getPixelValue(element,value)}var getFixedWidth=function(element,value){var borderBox=element.currentStyle["box-sizing"]=="border-box";var adjustment=0;if(quirksMode&&!borderBox){adjustment+=getBorderWidth(element)+getWidth(element,"padding")}else{if(!quirksMode&&borderBox){adjustment-=getBorderWidth(element)+getWidth(element,"padding")}}return getPixelWidth(element,value)+adjustment};function getBorderWidth(element){return element.offsetWidth-element.clientWidth}function getWidth(element,type){return getPixelWidth(element,element.currentStyle[type+"Left"])+getPixelWidth(element,element.currentStyle[type+"Right"])}HEADER+="*{minWidth:none;maxWidth:none;min-width:none;max-width:none}";layout.minWidth=function(element){if(element.currentStyle["min-width"]!=null){element.style.minWidth=element.currentStyle["min-width"]}if(register(arguments.callee,element,element.currentStyle.minWidth!="none")){layout.boxSizing(element);fixWidth(element);resizeWidth(element)}};eval("IE7.Layout.maxWidth="+String(layout.minWidth).replace(/min/g,"max"));function resizeWidth(element){var rect=element.getBoundingClientRect();var width=rect.right-rect.left;if(element.currentStyle.minWidth!="none"&&width<=getFixedWidth(element,element.currentStyle.minWidth)){element.runtimeStyle.width=element.currentStyle.minWidth}else{if(element.currentStyle.maxWidth!="none"&&width>=getFixedWidth(element,element.currentStyle.maxWidth)){element.runtimeStyle.width=element.currentStyle.maxWidth}else{element.runtimeStyle.width=element.runtimeStyle.fixedWidth}}}function fixRight(element){if(register(fixRight,element,/^(fixed|absolute)$/.test(element.currentStyle.position)&&getDefinedStyle(element,"left")!="auto"&&getDefinedStyle(element,"right")!="auto"&&AUTO.test(getDefinedStyle(element,"width")))){resizeRight(element);IE7.Layout.boxSizing(element)}}IE7.Layout.fixRight=fixRight;function resizeRight(element){var left=getPixelWidth(element,element.runtimeStyle._left||element.currentStyle.left);var width=layoutWidth(element)-getPixelWidth(element,element.currentStyle.right)-left-getWidth(element,"margin");if(parseInt(element.runtimeStyle.width)==width){return }element.runtimeStyle.width="";if(isFixed(element)||HEIGHT||element.offsetWidth=5.5&&appVersion<7){IE7.CSS.addFix(/background(-image)?\s*:\s*([^};]*)?url\(([^\)]+)\)([^;}]*)?/,function(match,$1,$2,url,$4){url=getString(url);return PNG.test(url)?"filter:"+format(PNG_FILTER,url,"crop")+";zoom:1;background"+($1||"")+":"+($2||"")+"none"+($4||""):match});IE7.HTML.addRecalc("img,input",function(element){if(element.tagName=="INPUT"&&element.type!="image"){return }fixImage(element);addEventHandler(element,"onpropertychange",function(){if(!printing&&event.propertyName=="src"&&element.src.indexOf(BLANK_GIF)==-1){fixImage(element)}})});var printing=false;addEventHandler(window,"onbeforeprint",function(){printing=true;for(var i=0;i=7){return }IE7.CSS.addRecalc("position","fixed",_positionFixed,"absolute");IE7.CSS.addRecalc("background(-attachment)?","[^};]*fixed",_backgroundFixed);var $viewport=quirksMode?"body":"documentElement";function _fixBackground(){if(body.currentStyle.backgroundAttachment!="fixed"){if(body.currentStyle.backgroundImage=="none"){body.runtimeStyle.backgroundRepeat="no-repeat";body.runtimeStyle.backgroundImage="url("+BLANK_GIF+")"}body.runtimeStyle.backgroundAttachment="fixed"}_fixBackground=Undefined}var _tmp=createTempElement("img");function _isFixed(element){return element?isFixed(element)||_isFixed(element.parentElement):false}function _setExpression(element,propertyName,expression){setTimeout("document.all."+element.uniqueID+".runtimeStyle.setExpression('"+propertyName+"','"+expression+"')",0)}function _backgroundFixed(element){if(register(_backgroundFixed,element,element.currentStyle.backgroundAttachment=="fixed"&&!element.contains(body))){_fixBackground();bgLeft(element);bgTop(element);_backgroundPosition(element)}}function _backgroundPosition(element){_tmp.src=element.currentStyle.backgroundImage.slice(5,-2);var parentElement=element.canHaveChildren?element:element.parentElement;parentElement.appendChild(_tmp);setOffsetLeft(element);setOffsetTop(element);parentElement.removeChild(_tmp)}function bgLeft(element){element.style.backgroundPositionX=element.currentStyle.backgroundPositionX;if(!_isFixed(element)){_setExpression(element,"backgroundPositionX","(parseInt(runtimeStyle.offsetLeft)+document."+$viewport+".scrollLeft)||0")}}eval(rotate(bgLeft));function setOffsetLeft(element){var propertyName=_isFixed(element)?"backgroundPositionX":"offsetLeft";element.runtimeStyle[propertyName]=getOffsetLeft(element,element.style.backgroundPositionX)-element.getBoundingClientRect().left-element.clientLeft+2}eval(rotate(setOffsetLeft));function getOffsetLeft(element,position){switch(position){case"left":case"top":return 0;case"right":case"bottom":return viewport.clientWidth-_tmp.offsetWidth;case"center":return(viewport.clientWidth-_tmp.offsetWidth)/2;default:if(PERCENT.test(position)){return parseInt((viewport.clientWidth-_tmp.offsetWidth)*parseFloat(position)/100)}_tmp.style.left=position;return _tmp.offsetLeft}}eval(rotate(getOffsetLeft));function _positionFixed(element){if(register(_positionFixed,element,isFixed(element))){setOverrideStyle(element,"position","absolute");setOverrideStyle(element,"left",element.currentStyle.left);setOverrideStyle(element,"top",element.currentStyle.top);_fixBackground();IE7.Layout.fixRight(element);_foregroundPosition(element)}}function _foregroundPosition(element,recalc){positionTop(element,recalc);positionLeft(element,recalc,true);if(!element.runtimeStyle.autoLeft&&element.currentStyle.marginLeft=="auto"&&element.currentStyle.right!="auto"){var left=viewport.clientWidth-getPixelWidth(element,element.currentStyle.right)-getPixelWidth(element,element.runtimeStyle._left)-element.clientWidth;if(element.currentStyle.marginRight=="auto"){left=parseInt(left/2)}if(_isFixed(element.offsetParent)){element.runtimeStyle.pixelLeft+=left}else{element.runtimeStyle.shiftLeft=left}}clipWidth(element);clipHeight(element)}function clipWidth(element){var fixWidth=element.runtimeStyle.fixWidth;element.runtimeStyle.borderRightWidth="";element.runtimeStyle.width=fixWidth?getPixelWidth(element,fixWidth):"";if(element.currentStyle.width!="auto"){var rect=element.getBoundingClientRect();var width=element.offsetWidth-viewport.clientWidth+rect.left-2;if(width>=0){element.runtimeStyle.borderRightWidth="0px";width=Math.max(getPixelValue(element,element.currentStyle.width)-width,0);setOverrideStyle(element,"width",width);return width}}}eval(rotate(clipWidth));function positionLeft(element,recalc){if(!recalc&&PERCENT.test(element.currentStyle.width)){element.runtimeStyle.fixWidth=element.currentStyle.width}if(element.runtimeStyle.fixWidth){element.runtimeStyle.width=getPixelWidth(element,element.runtimeStyle.fixWidth)}element.runtimeStyle.shiftLeft=0;element.runtimeStyle._left=element.currentStyle.left;element.runtimeStyle.autoLeft=element.currentStyle.right!="auto"&&element.currentStyle.left=="auto";element.runtimeStyle.left="";element.runtimeStyle.screenLeft=getScreenLeft(element);element.runtimeStyle.pixelLeft=element.runtimeStyle.screenLeft;if(!recalc&&!_isFixed(element.offsetParent)){_setExpression(element,"pixelLeft","runtimeStyle.screenLeft+runtimeStyle.shiftLeft+document."+$viewport+".scrollLeft")}}eval(rotate(positionLeft));function getScreenLeft(element){var screenLeft=element.offsetLeft,nested=1;if(element.runtimeStyle.autoLeft){screenLeft=viewport.clientWidth-element.offsetWidth-getPixelWidth(element,element.currentStyle.right)}if(element.currentStyle.marginLeft!="auto"){screenLeft-=getPixelWidth(element,element.currentStyle.marginLeft)}while(element=element.offsetParent){if(element.currentStyle.position!="static"){nested=-1}screenLeft+=element.offsetLeft*nested}return screenLeft}eval(rotate(getScreenLeft));function getPixelWidth(element,value){return PERCENT.test(value)?parseInt(parseFloat(value)/100*viewport.clientWidth):getPixelValue(element,value)}eval(rotate(getPixelWidth));function _resize(){var elements=_backgroundFixed.elements;for(var i in elements){_backgroundPosition(elements[i])}elements=_positionFixed.elements;for(i in elements){_foregroundPosition(elements[i],true);_foregroundPosition(elements[i],true)}_timer=0}var _timer;addResize(function(){if(!_timer){_timer=setTimeout(_resize,0)}})};var WRAPPER_STYLE={backgroundColor:"transparent",backgroundImage:"none",backgroundPositionX:null,backgroundPositionY:null,backgroundRepeat:null,borderTopWidth:0,borderRightWidth:0,borderBottomWidth:0,borderLeftStyle:"none",borderTopStyle:"none",borderRightStyle:"none",borderBottomStyle:"none",borderLeftWidth:0,height:null,marginTop:0,marginBottom:0,marginRight:0,marginLeft:0,width:"100%"};IE7.CSS.addRecalc("overflow","visible",function(element){if(element.parentNode.ie7_wrapped){return }if(IE7.Layout&&element.currentStyle["max-height"]!="auto"){IE7.Layout.maxHeight(element)}if(element.currentStyle.marginLeft=="auto"){element.style.marginLeft=0}if(element.currentStyle.marginRight=="auto"){element.style.marginRight=0}var wrapper=document.createElement(ANON);wrapper.ie7_wrapped=element;for(var propertyName in WRAPPER_STYLE){wrapper.style[propertyName]=element.currentStyle[propertyName];if(WRAPPER_STYLE[propertyName]!=null){element.runtimeStyle[propertyName]=WRAPPER_STYLE[propertyName]}}wrapper.style.display="block";wrapper.style.position="relative";element.runtimeStyle.position="absolute";element.parentNode.insertBefore(wrapper,element);wrapper.appendChild(element)});function ie7Quirks(){var FONT_SIZES="xx-small,x-small,small,medium,large,x-large,xx-large".split(",");for(var i=0;i=%2",a,b,position,last,"&&","%","==");if(negate){query="!("+query+")"}return query}_PSEUDO_CLASSES={link:"e%1.currentStyle['ie7-link']=='link'",visited:"e%1.currentStyle['ie7-link']=='visited'",checked:"e%1.checked",contains:"e%1.innerText.indexOf('%2')!=-1",disabled:"e%1.isDisabled",empty:"IE7._isEmpty(e%1)",enabled:"e%1.disabled===false","first-child":"!IE7._getPreviousElementSibling(e%1)",lang:"IE7._isLang(e%1,'%2')","last-child":"!IE7._getNextElementSibling(e%1)","only-child":"!IE7._getPreviousElementSibling(e%1)&&!IE7._getNextElementSibling(e%1)",target:"e%1.id==location.hash.slice(1)",indeterminate:"e%1.indeterminate"};IE7._register=function(element){if(element.rows){element.ie7_length=element.rows.length;element.ie7_lookup="rowIndex"}else{if(element.cells){element.ie7_length=element.cells.length;element.ie7_lookup="cellIndex"}else{if(element.ie7_indexed!=IE7._indexed){var index=0;var child=element.firstChild;while(child){if(child.nodeType==1&&child.nodeName!="!"){child.ie7_index=++index}child=child.nextSibling}element.ie7_length=index;element.ie7_lookup="ie7_index"}}}element.ie7_indexed=IE7._indexed;return element};var keys=cssParser[_KEYS];var pseudoClass=keys[keys.length-1];keys.length--;cssParser.merge({":not\\((\\*|[\\w-]+)?([^)]*)\\)":function(match,tagName,filters){var replacement=(tagName&&tagName!="*")?format("if(e%1.nodeName=='%2'){",_index,tagName.toUpperCase()):"";replacement+=cssParser.exec(filters);return"if(!"+replacement.slice(2,-1).replace(/\)\{if\(/g,"&&")+"){"},":nth(-last)?-child\\(([^)]+)\\)":function(match,last,args){_wild=false;last=format("e%1.parentNode.ie7_length",_index);var replacement="if(p%1!==e%1.parentNode)p%1=IE7._register(e%1.parentNode);";replacement+="var i=e%1[p%1.ie7_lookup];if(p%1.ie7_lookup!='ie7_index')i++;if(";return format(replacement,_index)+_nthChild(match,args,"i",last)+"){"}});keys.push(pseudoClass);var BRACKETS="\\([^)]*\\)";if(IE7.CSS.pseudoClasses){IE7.CSS.pseudoClasses+="|"}IE7.CSS.pseudoClasses+="before|after|last\\-child|only\\-child|empty|root|"+"not|nth\\-child|nth\\-last\\-child|contains|lang".split("|").join(BRACKETS+"|")+BRACKETS;encoder.add(/::/,":");var Focus=new DynamicPseudoClass("focus",function(element){var instance=arguments;IE7.CSS.addEventHandler(element,"onfocus",function(){Focus.unregister(instance);Focus.register(instance)});IE7.CSS.addEventHandler(element,"onblur",function(){Focus.unregister(instance)});if(element==document.activeElement){Focus.register(instance)}});var Active=new DynamicPseudoClass("active",function(element){var instance=arguments;IE7.CSS.addEventHandler(element,"onmousedown",function(){Active.register(instance)})});addEventHandler(document,"onmouseup",function(){var instances=Active.instances;for(var i in instances){Active.unregister(instances[i])}});var Checked=new DynamicPseudoClass("checked",function(element){if(typeof element.checked!="boolean"){return }var instance=arguments;IE7.CSS.addEventHandler(element,"onpropertychange",function(){if(event.propertyName=="checked"){if(element.checked){Checked.register(instance)}else{Checked.unregister(instance)}}});if(element.checked){Checked.register(instance)}});var Enabled=new DynamicPseudoClass("enabled",function(element){if(typeof element.disabled!="boolean"){return }var instance=arguments;IE7.CSS.addEventHandler(element,"onpropertychange",function(){if(event.propertyName=="disabled"){if(!element.isDisabled){Enabled.register(instance)}else{Enabled.unregister(instance)}}});if(!element.isDisabled){Enabled.register(instance)}});var Disabled=new DynamicPseudoClass("disabled",function(element){if(typeof element.disabled!="boolean"){return }var instance=arguments;IE7.CSS.addEventHandler(element,"onpropertychange",function(){if(event.propertyName=="disabled"){if(element.isDisabled){Disabled.register(instance)}else{Disabled.unregister(instance)}}});if(element.isDisabled){Disabled.register(instance)}});var Indeterminate=new DynamicPseudoClass("indeterminate",function(element){if(typeof element.indeterminate!="boolean"){return }var instance=arguments;IE7.CSS.addEventHandler(element,"onpropertychange",function(){if(event.propertyName=="indeterminate"){if(element.indeterminate){Indeterminate.register(instance)}else{Indeterminate.unregister(instance)}}});IE7.CSS.addEventHandler(element,"onclick",function(){Indeterminate.unregister(instance)})});var Target=new DynamicPseudoClass("target",function(element){var instance=arguments;if(!element.tabIndex){element.tabIndex=0}IE7.CSS.addEventHandler(document,"onpropertychange",function(){if(event.propertyName=="activeElement"){if(element.id&&element.id==location.hash.slice(1)){Target.register(instance)}else{Target.unregister(instance)}}});if(element.id&&element.id==location.hash.slice(1)){Target.register(instance)}});var ATTR=/^attr/;var URL=/^url\s*\(\s*([^)]*)\)$/;var POSITION_MAP={before0:"beforeBegin",before1:"afterBegin",after0:"afterEnd",after1:"beforeEnd"};var PseudoElement=IE7.PseudoElement=Rule.extend({constructor:function(selector,position,cssText){this.position=position;var content=cssText.match(PseudoElement.CONTENT),match,entity;if(content){content=content[1];match=content.split(/\s+/);for(var i=0;(entity=match[i]);i++){match[i]=ATTR.test(entity)?{attr:entity.slice(5,-1)}:(entity.charAt(0)=="'")?getString(entity):decode(entity)}content=match}this.content=content;this.base(selector,decode(cssText))},init:function(){this.match=cssQuery(this.selector);for(var i=0;i%4",MATCH:/(.*):(before|after).*/,count:0});var UNSUCCESSFUL=/^(submit|reset|button)$/;IE7.HTML.addRecalc("button,input",function(button){if(button.tagName=="BUTTON"){var match=button.outerHTML.match(/ value="([^"]*)"/i);button.runtimeStyle.value=(match)?match[1]:""}if(button.type=="submit"){addEventHandler(button,"onclick",function(){button.runtimeStyle.clicked=true;setTimeout("document.all."+button.uniqueID+".runtimeStyle.clicked=false",1)})}});IE7.HTML.addRecalc("form",function(form){addEventHandler(form,"onsubmit",function(){for(var element,i=0;element=form[i];i++){if(UNSUCCESSFUL.test(element.type)&&!element.disabled&&!element.runtimeStyle.clicked){element.disabled=true;setTimeout("document.all."+element.uniqueID+".disabled=false",1)}else{if(element.tagName=="BUTTON"&&element.type=="submit"){setTimeout("document.all."+element.uniqueID+".value='"+element.value+"'",1);element.value=element.runtimeStyle.value}}}})});IE7.HTML.addRecalc("img",function(img){if(img.alt&&!img.title){img.title=""}});IE7.CSS.addRecalc("border-spacing",NUMERIC,function(element){if(element.currentStyle.borderCollapse!="collapse"){element.cellSpacing=getPixelValue(element,element.currentStyle["border-spacing"])}});IE7.CSS.addRecalc("box-sizing","content-box",IE7.Layout.boxSizing);IE7.CSS.addRecalc("box-sizing","border-box",IE7.Layout.borderBox);IE7.CSS.addFix(/opacity\s*:\s*([\d.]+)/,function(match,value){return"zoom:1;filter:Alpha(opacity="+((value*100)||1)+")"});var IMAGE=/^image/i;IE7.HTML.addRecalc("object",function(element){if(IMAGE.test(element.type)){element.body.style.cssText="margin:0;padding:0;border:none;overflow:hidden";return element}});IE7.loaded=true;(function(){try{documentElement.doScroll("left")}catch(e){setTimeout(arguments.callee,1);return }try{eval(script.innerHTML)}catch(e){}PNG=new RegExp(rescape(typeof IE7_PNG_SUFFIX=="string"?IE7_PNG_SUFFIX:"-trans.png")+"$","i");body=document.body;viewport=quirksMode?body:documentElement;body.className+=" ie7_body";documentElement.className+=" ie7_html";if(quirksMode){ie7Quirks()}IE7.CSS.init();IE7.HTML.init();IE7.HTML.apply();IE7.CSS.apply();IE7.recalc()})()})(); \ No newline at end of file +(function(){IE7={toString:function(){return"IE7 version 2.0 (beta3)"}};var appVersion=IE7.appVersion=navigator.appVersion.match(/MSIE (\d\.\d)/)[1];if(/ie7_off/.test(top.location.search)||appVersion<5){return}var Undefined=K();var quirksMode=document.compatMode!="CSS1Compat";var documentElement=document.documentElement,body,viewport;var ANON="!";var HEADER=":link{ie7-link:link}:visited{ie7-link:visited}";var RELATIVE=/^[\w\.]+[^:]*$/;function makePath(href,path){if(RELATIVE.test(href)){href=(path||"")+href}return href}function getPath(href,path){href=makePath(href,path);return href.slice(0,href.lastIndexOf("/")+1)}var script=document.scripts[document.scripts.length-1];var path=getPath(script.src);try{var httpRequest=new ActiveXObject("Microsoft.XMLHTTP")}catch(e){}var fileCache={};function loadFile(href,path){try{href=makePath(href,path);if(!fileCache[href]){httpRequest.open("GET",href,false);httpRequest.send();if(httpRequest.status==0||httpRequest.status==200){fileCache[href]=httpRequest.responseText}}}catch(e){}finally{return fileCache[href]||""}}if(appVersion<5.5){undefined=Undefined();ANON="HTML:!";var GLOBAL=/(g|gi)$/;var _String_replace=String.prototype.replace;String.prototype.replace=function(expression,replacement){if(typeof replacement=="function"){if(expression&&expression.constructor==RegExp){var regexp=expression;var global=regexp.global;if(global==null){global=GLOBAL.test(regexp)}if(global){regexp=new RegExp(regexp.source)}}else{regexp=new RegExp(rescape(expression))}var match,string=this,result="";while(string&&(match=regexp.exec(string))){result+=string.slice(0,match.index)+replacement.apply(this,match);string=string.slice(match.index+match[0].length);if(!global){break}}return result+string}return _String_replace.apply(this,arguments)};Array.prototype.pop=function(){if(this.length){var i=this[this.length-1];this.length--;return i}return undefined};Array.prototype.push=function(){for(var i=0;i":"","\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\/":"","@(namespace|import)[^;\\n]+[;\\n]":"","'(\\\\.|[^'\\\\])*'":encodeString,'"(\\\\.|[^"\\\\])*"':encodeString,"\\s+":" "});function encode(cssText){return encoder.exec(cssText)}function decode(cssText){return cssText.replace(ENCODED,function(match,index){return _strings[index-1]})}function encodeString(string){return"\x01"+_strings.push(string.replace(UNICODE,function(match,chr){return eval("'\\u"+"0000".slice(chr.length)+chr+"'")}).slice(1,-1).replace(QUOTES,"\\'"))}function getString(value){return STRING.test(value)?_strings[value.slice(1)-1]:value}var rotater=new RegGrp({Width:"Height",width:"height",Left:"Top",left:"top",Right:"Bottom",right:"bottom",onX:"onY"});function rotate(fn){return rotater.exec(fn)}var eventHandlers=[];function addResize(handler){addRecalc(handler);addEventHandler(window,"onresize",handler)}function addEventHandler(element,type,handler){element.attachEvent(type,handler);eventHandlers.push(arguments)}function removeEventHandler(element,type,handler){try{element.detachEvent(type,handler)}catch(ignore){}}addEventHandler(window,"onunload",function(){var handler;while(handler=eventHandlers.pop()){removeEventHandler(handler[0],handler[1],handler[2])}});function register(handler,element,condition){if(!handler.elements){handler.elements={}}if(condition){handler.elements[element.uniqueID]=element}else{delete handler.elements[element.uniqueID]}return condition}addEventHandler(window,"onbeforeprint",function(){if(!IE7.CSS.print){new StyleSheet("print")}IE7.CSS.print.recalc()});var PIXEL=/^\d+(px)?$/i;var PERCENT=/^\d+%$/;var getPixelValue=function(element,value){if(PIXEL.test(value)){return parseInt(value)}var style=element.style.left;var runtimeStyle=element.runtimeStyle.left;element.runtimeStyle.left=element.currentStyle.left;element.style.left=value||0;value=element.style.pixelLeft;element.style.left=style;element.runtimeStyle.left=runtimeStyle;return value};var $IE7="ie7-";var Fix=Base.extend({constructor:function(){this.fixes=[];this.recalcs=[]},init:Undefined});var recalcs=[];function addRecalc(recalc){recalcs.push(recalc)}IE7.recalc=function(){IE7.HTML.recalc();IE7.CSS.recalc();for(var i=0;i1?2:0;var block=cssParser.exec(selectors[i])||"if(0){";if(_wild){block+=format("if(e%1.nodeName!='!'){",_index)}var store=_duplicate>1?_TEST:"";block+=format(store+_STORE,_index);block+=Array(match(block,/\{/g).length+1).join("}");fn+=block}eval(format(_FN,reg)+cssParser.unescape(fn)+"return s?null:r}");_cache[selector]=_selectorFunction}return _cache[selector](context||document,single)}var _MSIE5=appVersion<6;var _EVALUATED=/^(href|src)$/;var _ATTRIBUTES={"class":"className","for":"htmlFor"};IE7._indexed=1;IE7._byId=function(document,id){var result=document.all[id]||null;if(!result||result.id==id){return result}for(var i=0;i+~,]|[^(]\+|^)([#.:\[])/g,IMPLIED_SPACE=/(^|,)([^\s>+~])/g,WHITESPACE=/\s*([\s>+~(),]|^|$)\s*/g,WILD_CARD=/\s\*\s/g;var CSSParser=RegGrp.extend({constructor:function(items){this.base(items);this.sorter=new RegGrp;this.sorter.add(/:not\([^)]*\)/,RegGrp.IGNORE);this.sorter.add(/([ >](\*|[\w-]+))([^: >+~]*)(:\w+-child(\([^)]+\))?)([^: >+~]*)/,"$1$3$6$4")},ignoreCase:true,escape:function(selector){return this.optimise(this.format(selector))},format:function(selector){return selector.replace(WHITESPACE,"$1").replace(IMPLIED_SPACE,"$1 $2").replace(IMPLIED_ASTERISK,"$1*$2")},optimise:function(selector){return this.sorter.exec(selector.replace(WILD_CARD,">* "))},unescape:function(selector){return decode(selector)}});var _OPERATORS={"":"%1!=null","=":"%1=='%2'","~=":/(^| )%1( |$)/,"|=":/^%1(-|$)/,"^=":/^%1/,"$=":/%1$/,"*=":/%1/};var _PSEUDO_CLASSES={"first-child":"!IE7._getPreviousElementSibling(e%1)",link:"e%1.currentStyle['ie7-link']=='link'",visited:"e%1.currentStyle['ie7-link']=='visited'"};var _VAR="var p%2=0,i%2,e%2,n%2=e%1.";var _ID="e%1.sourceIndex";var _TEST="var g="+_ID+";if(!p[g]){p[g]=1;";var _STORE="r[r.length]=e%1;if(s)return e%1;";var _FN="var _selectorFunction=function(e0,s){IE7._indexed++;var r=[],p={},reg=[%1],d=document;";var reg;var _index;var _wild;var _list;var _duplicate;var _cache={};var cssParser=new CSSParser({" (\\*|[\\w-]+)#([\\w-]+)":function(match,tagName,id){_wild=false;var replacement="var e%2=IE7._byId(d,'%4');if(e%2&&";if(tagName!="*"){replacement+="e%2.nodeName=='%3'&&"}replacement+="(e%1==d||e%1.contains(e%2))){";if(_list){replacement+=format("i%1=n%1.length;",_list)}return format(replacement,_index++,_index,tagName.toUpperCase(),id)}," (\\*|[\\w-]+)":function(match,tagName){_duplicate++;_wild=tagName=="*";var replacement=_VAR;replacement+=(_wild&&_MSIE5)?"all":"getElementsByTagName('%3')";replacement+=";for(i%2=0;(e%2=n%2[i%2]);i%2++){";return format(replacement,_index++,_list=_index,tagName.toUpperCase())},">(\\*|[\\w-]+)":function(match,tagName){var children=_list;_wild=tagName=="*";var replacement=_VAR;replacement+=children?"children":"childNodes";if(!_wild&&children){replacement+=".tags('%3')"}replacement+=";for(i%2=0;(e%2=n%2[i%2]);i%2++){";if(_wild){replacement+="if(e%2.nodeType==1){";_wild=_MSIE5}else{if(!children){replacement+="if(e%2.nodeName=='%3'){"}}return format(replacement,_index++,_list=_index,tagName.toUpperCase())},"\\+(\\*|[\\w-]+)":function(match,tagName){var replacement="";if(_wild){replacement+="if(e%1.nodeName!='!'){"}_wild=false;replacement+="e%1=IE7._getNextElementSibling(e%1);if(e%1";if(tagName!="*"){replacement+="&&e%1.nodeName=='%2'"}replacement+="){";return format(replacement,_index,tagName.toUpperCase())},"~(\\*|[\\w-]+)":function(match,tagName){var replacement="";if(_wild){replacement+="if(e%1.nodeName!='!'){"}_wild=false;_duplicate=2;replacement+="while(e%1=e%1.nextSibling){if(e%1.ie7_adjacent==IE7._indexed)break;if(";if(tagName=="*"){replacement+="e%1.nodeType==1";if(_MSIE5){replacement+="&&e%1.nodeName!='!'"}}else{replacement+="e%1.nodeName=='%2'"}replacement+="){e%1.ie7_adjacent=IE7._indexed;";return format(replacement,_index,tagName.toUpperCase())},"#([\\w-]+)":function(match,id){_wild=false;var replacement="if(e%1.id=='%2'){";if(_list){replacement+=format("i%1=n%1.length;",_list)}return format(replacement,_index,id)},"\\.([\\w-]+)":function(match,className){_wild=false;reg.push(new RegExp("(^|\\s)"+rescape(className)+"(\\s|$)"));return format("if(e%1.className&®[%2].test(e%1.className)){",_index,reg.length-1)},"\\[([\\w-]+)\\s*([^=]?=)?\\s*([^\\]]*)\\]":function(match,attr,operator,value){var alias=_ATTRIBUTES[attr]||attr;if(operator){var getAttribute="e%1.getAttribute('%2',2)";if(!_EVALUATED.test(attr)){getAttribute="e%1.%3||"+getAttribute}attr=format("("+getAttribute+")",_index,attr,alias)}else{attr=format("IE7._getAttribute(e%1,'%2')",_index,attr)}var replacement=_OPERATORS[operator||""]||"0";if(replacement&&replacement.source){reg.push(new RegExp(format(replacement.source,rescape(cssParser.unescape(value)))));replacement="reg[%2].test(%1)";value=reg.length-1}return"if("+format(replacement,attr,value)+"){"},":+([\\w-]+)(\\(([^)]+)\\))?":function(match,pseudoClass,$2,args){pseudoClass=_PSEUDO_CLASSES[pseudoClass];return"if("+(pseudoClass?format(pseudoClass,_index,args||""):"0")+"){"}});var HYPERLINK=/a(#[\w-]+)?(\.[\w-]+)?:(hover|active)/i;var BRACE1=/\s*\{\s*/,BRACE2=/\s*\}\s*/,COMMA=/\s*\,\s*/;var FIRST_LINE_LETTER=/(.*)(:first-(line|letter))/;var styleSheets=document.styleSheets;IE7.CSS=new (Fix.extend({parser:new Parser,screen:"",print:"",styles:[],rules:[],pseudoClasses:appVersion<7?"first\\-child":"",dynamicPseudoClasses:{toString:function(){var strings=[];for(var pseudoClass in this){strings.push(pseudoClass)}return strings.join("|")}},init:function(){var NONE="^\x01$";var CLASS="\\[class=?[^\\]]*\\]";var pseudoClasses=[];if(this.pseudoClasses){pseudoClasses.push(this.pseudoClasses)}var dynamicPseudoClasses=this.dynamicPseudoClasses.toString();if(dynamicPseudoClasses){pseudoClasses.push(dynamicPseudoClasses)}pseudoClasses=pseudoClasses.join("|");var unknown=appVersion<7?["[>+~[(]|([:.])\\w+\\1"]:[CLASS];if(pseudoClasses){unknown.push(":("+pseudoClasses+")")}this.UNKNOWN=new RegExp(unknown.join("|")||NONE,"i");var complex=appVersion<7?["\\[[^\\]]+\\]|[^\\s(\\[]+\\s*[+~]"]:[CLASS];var complexRule=complex.concat();if(pseudoClasses){complexRule.push(":("+pseudoClasses+")")}Rule.COMPLEX=new RegExp(complexRule.join("|")||NONE,"ig");if(this.pseudoClasses){complex.push(":("+this.pseudoClasses+")")}DynamicRule.COMPLEX=new RegExp(complex.join("|")||NONE,"i");DynamicRule.MATCH=new RegExp(dynamicPseudoClasses?"(.*):("+dynamicPseudoClasses+")(.*)":NONE,"i");this.createStyleSheet();this.refresh()},addEventHandler:function(){addEventHandler.apply(null,arguments)},addFix:function(expression,replacement){this.parser.add(expression,replacement)},addRecalc:function(propertyName,test,handler,replacement){test=new RegExp("([{;\\s])"+propertyName+"\\s*:\\s*"+test+"[^;}]*");var id=this.recalcs.length;if(replacement){replacement=propertyName+":"+replacement}this.addFix(test,function(match,$1){return(replacement?$1+replacement:match)+";ie7-"+match.slice(1)+";ie7_recalc"+id+":1"});this.recalcs.push(arguments);return id},apply:function(){this.getInlineStyles();new StyleSheet("screen");this.trash()},createStyleSheet:function(){this.styleSheet=document.createStyleSheet();this.styleSheet.ie7=true;this.styleSheet.owningElement.ie7=true;this.styleSheet.cssText=HEADER},getInlineStyles:function(){var styleSheets=document.getElementsByTagName("style"),styleSheet;for(var i=styleSheets.length-1;(styleSheet=styleSheets[i]);i--){if(!styleSheet.disabled&&!styleSheet.ie7){this.styles.push(styleSheet.innerHTML)}}},getText:function(styleSheet,path){try{var cssText=styleSheet.cssText}catch(e){cssText=""}if(httpRequest){cssText=loadFile(styleSheet.href,path)||cssText}return cssText},recalc:function(){this.screen.recalc();var RECALCS=/ie7_recalc\d+/g;var start=HEADER.match(/[{,]/g).length;var stop=start+(this.screen.cssText.match(/\{/g)||"").length;var rules=this.styleSheet.rules,rule;var calcs,calc,elements,element,i,j,k,id;for(i=start;i0&&Rule.CLASS.test(simple)){simple=simple.replace(Rule.CLASS,"");classes--}while(tags>0&&Rule.TAG.test(simple)){simple=simple.replace(Rule.TAG,"$1*");tags--}simple+="."+this.className;classes=Math.min(classes,2);tags=Math.min(tags,2);var score=-10*classes-tags;if(score>0){simple=simple+","+Rule.MAP[score]+" "+simple}return simple},remove:function(element){element.className=element.className.replace(this.MATCH,"$1")},toString:function(){return format("%1 {%2}",this.selectorText,this.cssText)}},{CHILD:/>/g,CLASS:/\.[\w-]+/,CLASSES:/[.:\[]/g,MULTI:/(\.[\w-]+)+/g,PREFIX:"ie7_class",TAG:/^\w+|([\s>+~])\w+/,TAGS:/^\w|[\s>+~]\w/g,MAP:{1:"html",2:"html body",10:".ie7_html",11:"html.ie7_html",12:"html.ie7_html body",20:".ie7_html .ie7_body",21:"html.ie7_html .ie7_body",22:"html.ie7_html body.ie7_body"}});var DynamicRule=Rule.extend({constructor:function(selector,attach,dynamicPseudoClass,target,cssText){this.attach=attach||"*";this.dynamicPseudoClass=IE7.CSS.dynamicPseudoClasses[dynamicPseudoClass];this.target=target;this.base(selector,cssText)},recalc:function(){var attaches=cssQuery(this.attach),attach;for(var i=0;attach=attaches[i];i++){var target=this.target?cssQuery(this.target,attach):[attach];if(target.length){this.dynamicPseudoClass.apply(attach,target,this)}}}});var DynamicPseudoClass=Base.extend({constructor:function(name,apply){this.name=name;this.apply=apply;this.instances={};IE7.CSS.dynamicPseudoClasses[name]=this},register:function(instance){var _class=instance[2];instance.id=_class.id+instance[0].uniqueID;if(!this.instances[instance.id]){var target=instance[1],j;for(j=0;j*:"+(type=="marginTop"?"first":"last")+"-child",element,true);if(child&&child.currentStyle.styleFloat=="none"&&IE7.hasLayout(child)){collapseMargin(child,type);margin=_getMargin(element,element.currentStyle[type]);childMargin=_getMargin(child,child.currentStyle[type]);if(margin<0||childMargin<0){element.runtimeStyle[type]=margin+childMargin}else{element.runtimeStyle[type]=Math.max(childMargin,margin)}child.runtimeStyle[type]="0px"}}}function _getMargin(element,value){return value=="auto"?0:getPixelValue(element,value)}var UNIT=/^[.\d][\w%]*$/,AUTO=/^(auto|0cm)$/;var applyWidth,applyHeight;IE7.Layout.borderBox=function(element){applyWidth(element);applyHeight(element)};var fixWidth=function(HEIGHT){applyWidth=function(element){if(!PERCENT.test(element.currentStyle.width)){fixWidth(element)}collapseMargins(element)};function fixWidth(element,value){if(!element.runtimeStyle.fixedWidth){if(!value){value=element.currentStyle.width}element.runtimeStyle.fixedWidth=(UNIT.test(value))?Math.max(0,getFixedWidth(element,value)):value;setOverrideStyle(element,"width",element.runtimeStyle.fixedWidth)}}function layoutWidth(element){if(!isFixed(element)){var layoutParent=element.offsetParent;while(layoutParent&&!IE7.hasLayout(layoutParent)){layoutParent=layoutParent.offsetParent}}return(layoutParent||viewport).clientWidth}function getPixelWidth(element,value){if(PERCENT.test(value)){return parseInt(parseFloat(value)/100*layoutWidth(element))}return getPixelValue(element,value)}var getFixedWidth=function(element,value){var borderBox=element.currentStyle["box-sizing"]=="border-box";var adjustment=0;if(quirksMode&&!borderBox){adjustment+=getBorderWidth(element)+getWidth(element,"padding")}else{if(!quirksMode&&borderBox){adjustment-=getBorderWidth(element)+getWidth(element,"padding")}}return getPixelWidth(element,value)+adjustment};function getBorderWidth(element){return element.offsetWidth-element.clientWidth}function getWidth(element,type){return getPixelWidth(element,element.currentStyle[type+"Left"])+getPixelWidth(element,element.currentStyle[type+"Right"])}HEADER+="*{minWidth:none;maxWidth:none;min-width:none;max-width:none}";layout.minWidth=function(element){if(element.currentStyle["min-width"]!=null){element.style.minWidth=element.currentStyle["min-width"]}if(register(arguments.callee,element,element.currentStyle.minWidth!="none")){layout.boxSizing(element);fixWidth(element);resizeWidth(element)}};eval("IE7.Layout.maxWidth="+String(layout.minWidth).replace(/min/g,"max"));function resizeWidth(element){var rect=element.getBoundingClientRect();var width=rect.right-rect.left;if(element.currentStyle.minWidth!="none"&&width<=getFixedWidth(element,element.currentStyle.minWidth)){element.runtimeStyle.width=element.currentStyle.minWidth}else{if(element.currentStyle.maxWidth!="none"&&width>=getFixedWidth(element,element.currentStyle.maxWidth)){element.runtimeStyle.width=element.currentStyle.maxWidth}else{element.runtimeStyle.width=element.runtimeStyle.fixedWidth}}}function fixRight(element){if(register(fixRight,element,/^(fixed|absolute)$/.test(element.currentStyle.position)&&getDefinedStyle(element,"left")!="auto"&&getDefinedStyle(element,"right")!="auto"&&AUTO.test(getDefinedStyle(element,"width")))){resizeRight(element);IE7.Layout.boxSizing(element)}}IE7.Layout.fixRight=fixRight;function resizeRight(element){var left=getPixelWidth(element,element.runtimeStyle._left||element.currentStyle.left);var width=layoutWidth(element)-getPixelWidth(element,element.currentStyle.right)-left-getWidth(element,"margin");if(parseInt(element.runtimeStyle.width)==width){return}element.runtimeStyle.width="";if(isFixed(element)||HEIGHT||element.offsetWidth=5.5&&appVersion<7){IE7.CSS.addFix(/background(-image)?\s*:\s*([^};]*)?url\(([^\)]+)\)([^;}]*)?/,function(match,$1,$2,url,$4){url=getString(url);return PNG.test(url)?"filter:"+format(PNG_FILTER,url,"crop")+";zoom:1;background"+($1||"")+":"+($2||"")+"none"+($4||""):match});IE7.HTML.addRecalc("img,input",function(element){if(element.tagName=="INPUT"&&element.type!="image"){return}fixImage(element);addEventHandler(element,"onpropertychange",function(){if(!printing&&event.propertyName=="src"&&element.src.indexOf(BLANK_GIF)==-1){fixImage(element)}})});var printing=false;addEventHandler(window,"onbeforeprint",function(){printing=true;for(var i=0;i=7){return}IE7.CSS.addRecalc("position","fixed",_positionFixed,"absolute");IE7.CSS.addRecalc("background(-attachment)?","[^};]*fixed",_backgroundFixed);var $viewport=quirksMode?"body":"documentElement";function _fixBackground(){if(body.currentStyle.backgroundAttachment!="fixed"){if(body.currentStyle.backgroundImage=="none"){body.runtimeStyle.backgroundRepeat="no-repeat";body.runtimeStyle.backgroundImage="url("+BLANK_GIF+")"}body.runtimeStyle.backgroundAttachment="fixed"}_fixBackground=Undefined}var _tmp=createTempElement("img");function _isFixed(element){return element?isFixed(element)||_isFixed(element.parentElement):false}function _setExpression(element,propertyName,expression){setTimeout("document.all."+element.uniqueID+".runtimeStyle.setExpression('"+propertyName+"','"+expression+"')",0)}function _backgroundFixed(element){if(register(_backgroundFixed,element,element.currentStyle.backgroundAttachment=="fixed"&&!element.contains(body))){_fixBackground();bgLeft(element);bgTop(element);_backgroundPosition(element)}}function _backgroundPosition(element){_tmp.src=element.currentStyle.backgroundImage.slice(5,-2);var parentElement=element.canHaveChildren?element:element.parentElement;parentElement.appendChild(_tmp);setOffsetLeft(element);setOffsetTop(element);parentElement.removeChild(_tmp)}function bgLeft(element){element.style.backgroundPositionX=element.currentStyle.backgroundPositionX;if(!_isFixed(element)){_setExpression(element,"backgroundPositionX","(parseInt(runtimeStyle.offsetLeft)+document."+$viewport+".scrollLeft)||0")}}eval(rotate(bgLeft));function setOffsetLeft(element){var propertyName=_isFixed(element)?"backgroundPositionX":"offsetLeft";element.runtimeStyle[propertyName]=getOffsetLeft(element,element.style.backgroundPositionX)-element.getBoundingClientRect().left-element.clientLeft+2}eval(rotate(setOffsetLeft));function getOffsetLeft(element,position){switch(position){case"left":case"top":return 0;case"right":case"bottom":return viewport.clientWidth-_tmp.offsetWidth;case"center":return(viewport.clientWidth-_tmp.offsetWidth)/2;default:if(PERCENT.test(position)){return parseInt((viewport.clientWidth-_tmp.offsetWidth)*parseFloat(position)/100)}_tmp.style.left=position;return _tmp.offsetLeft}}eval(rotate(getOffsetLeft));function _positionFixed(element){if(register(_positionFixed,element,isFixed(element))){setOverrideStyle(element,"position","absolute");setOverrideStyle(element,"left",element.currentStyle.left);setOverrideStyle(element,"top",element.currentStyle.top);_fixBackground();IE7.Layout.fixRight(element);_foregroundPosition(element)}}function _foregroundPosition(element,recalc){positionTop(element,recalc);positionLeft(element,recalc,true);if(!element.runtimeStyle.autoLeft&&element.currentStyle.marginLeft=="auto"&&element.currentStyle.right!="auto"){var left=viewport.clientWidth-getPixelWidth(element,element.currentStyle.right)-getPixelWidth(element,element.runtimeStyle._left)-element.clientWidth;if(element.currentStyle.marginRight=="auto"){left=parseInt(left/2)}if(_isFixed(element.offsetParent)){element.runtimeStyle.pixelLeft+=left}else{element.runtimeStyle.shiftLeft=left}}clipWidth(element);clipHeight(element)}function clipWidth(element){var fixWidth=element.runtimeStyle.fixWidth;element.runtimeStyle.borderRightWidth="";element.runtimeStyle.width=fixWidth?getPixelWidth(element,fixWidth):"";if(element.currentStyle.width!="auto"){var rect=element.getBoundingClientRect();var width=element.offsetWidth-viewport.clientWidth+rect.left-2;if(width>=0){element.runtimeStyle.borderRightWidth="0px";width=Math.max(getPixelValue(element,element.currentStyle.width)-width,0);setOverrideStyle(element,"width",width);return width}}}eval(rotate(clipWidth));function positionLeft(element,recalc){if(!recalc&&PERCENT.test(element.currentStyle.width)){element.runtimeStyle.fixWidth=element.currentStyle.width}if(element.runtimeStyle.fixWidth){element.runtimeStyle.width=getPixelWidth(element,element.runtimeStyle.fixWidth)}element.runtimeStyle.shiftLeft=0;element.runtimeStyle._left=element.currentStyle.left;element.runtimeStyle.autoLeft=element.currentStyle.right!="auto"&&element.currentStyle.left=="auto";element.runtimeStyle.left="";element.runtimeStyle.screenLeft=getScreenLeft(element);element.runtimeStyle.pixelLeft=element.runtimeStyle.screenLeft;if(!recalc&&!_isFixed(element.offsetParent)){_setExpression(element,"pixelLeft","runtimeStyle.screenLeft+runtimeStyle.shiftLeft+document."+$viewport+".scrollLeft")}}eval(rotate(positionLeft));function getScreenLeft(element){var screenLeft=element.offsetLeft,nested=1;if(element.runtimeStyle.autoLeft){screenLeft=viewport.clientWidth-element.offsetWidth-getPixelWidth(element,element.currentStyle.right)}if(element.currentStyle.marginLeft!="auto"){screenLeft-=getPixelWidth(element,element.currentStyle.marginLeft)}while(element=element.offsetParent){if(element.currentStyle.position!="static"){nested=-1}screenLeft+=element.offsetLeft*nested}return screenLeft}eval(rotate(getScreenLeft));function getPixelWidth(element,value){return PERCENT.test(value)?parseInt(parseFloat(value)/100*viewport.clientWidth):getPixelValue(element,value)}eval(rotate(getPixelWidth));function _resize(){var elements=_backgroundFixed.elements;for(var i in elements){_backgroundPosition(elements[i])}elements=_positionFixed.elements;for(i in elements){_foregroundPosition(elements[i],true);_foregroundPosition(elements[i],true)}_timer=0}var _timer;addResize(function(){if(!_timer){_timer=setTimeout(_resize,0)}})};var WRAPPER_STYLE={backgroundColor:"transparent",backgroundImage:"none",backgroundPositionX:null,backgroundPositionY:null,backgroundRepeat:null,borderTopWidth:0,borderRightWidth:0,borderBottomWidth:0,borderLeftStyle:"none",borderTopStyle:"none",borderRightStyle:"none",borderBottomStyle:"none",borderLeftWidth:0,height:null,marginTop:0,marginBottom:0,marginRight:0,marginLeft:0,width:"100%"};IE7.CSS.addRecalc("overflow","visible",function(element){if(element.parentNode.ie7_wrapped){return}if(IE7.Layout&&element.currentStyle["max-height"]!="auto"){IE7.Layout.maxHeight(element)}if(element.currentStyle.marginLeft=="auto"){element.style.marginLeft=0}if(element.currentStyle.marginRight=="auto"){element.style.marginRight=0}var wrapper=document.createElement(ANON);wrapper.ie7_wrapped=element;for(var propertyName in WRAPPER_STYLE){wrapper.style[propertyName]=element.currentStyle[propertyName];if(WRAPPER_STYLE[propertyName]!=null){element.runtimeStyle[propertyName]=WRAPPER_STYLE[propertyName]}}wrapper.style.display="block";wrapper.style.position="relative";element.runtimeStyle.position="absolute";element.parentNode.insertBefore(wrapper,element);wrapper.appendChild(element)});function ie7Quirks(){var FONT_SIZES="xx-small,x-small,small,medium,large,x-large,xx-large".split(",");for(var i=0;i=%2",a,b,position,last,"&&","%","==");if(negate){query="!("+query+")"}return query}_PSEUDO_CLASSES={link:"e%1.currentStyle['ie7-link']=='link'",visited:"e%1.currentStyle['ie7-link']=='visited'",checked:"e%1.checked",contains:"e%1.innerText.indexOf('%2')!=-1",disabled:"e%1.isDisabled",empty:"IE7._isEmpty(e%1)",enabled:"e%1.disabled===false","first-child":"!IE7._getPreviousElementSibling(e%1)",lang:"IE7._isLang(e%1,'%2')","last-child":"!IE7._getNextElementSibling(e%1)","only-child":"!IE7._getPreviousElementSibling(e%1)&&!IE7._getNextElementSibling(e%1)",target:"e%1.id==location.hash.slice(1)",indeterminate:"e%1.indeterminate"};IE7._register=function(element){if(element.rows){element.ie7_length=element.rows.length;element.ie7_lookup="rowIndex"}else{if(element.cells){element.ie7_length=element.cells.length;element.ie7_lookup="cellIndex"}else{if(element.ie7_indexed!=IE7._indexed){var index=0;var child=element.firstChild;while(child){if(child.nodeType==1&&child.nodeName!="!"){child.ie7_index=++index}child=child.nextSibling}element.ie7_length=index;element.ie7_lookup="ie7_index"}}}element.ie7_indexed=IE7._indexed;return element};var keys=cssParser[_KEYS];var pseudoClass=keys[keys.length-1];keys.length--;cssParser.merge({":not\\((\\*|[\\w-]+)?([^)]*)\\)":function(match,tagName,filters){var replacement=(tagName&&tagName!="*")?format("if(e%1.nodeName=='%2'){",_index,tagName.toUpperCase()):"";replacement+=cssParser.exec(filters);return"if(!"+replacement.slice(2,-1).replace(/\)\{if\(/g,"&&")+"){"},":nth(-last)?-child\\(([^)]+)\\)":function(match,last,args){_wild=false;last=format("e%1.parentNode.ie7_length",_index);var replacement="if(p%1!==e%1.parentNode)p%1=IE7._register(e%1.parentNode);";replacement+="var i=e%1[p%1.ie7_lookup];if(p%1.ie7_lookup!='ie7_index')i++;if(";return format(replacement,_index)+_nthChild(match,args,"i",last)+"){"}});keys.push(pseudoClass);var BRACKETS="\\([^)]*\\)";if(IE7.CSS.pseudoClasses){IE7.CSS.pseudoClasses+="|"}IE7.CSS.pseudoClasses+="before|after|last\\-child|only\\-child|empty|root|"+"not|nth\\-child|nth\\-last\\-child|contains|lang".split("|").join(BRACKETS+"|")+BRACKETS;encoder.add(/::/,":");var Focus=new DynamicPseudoClass("focus",function(element){var instance=arguments;IE7.CSS.addEventHandler(element,"onfocus",function(){Focus.unregister(instance);Focus.register(instance)});IE7.CSS.addEventHandler(element,"onblur",function(){Focus.unregister(instance)});if(element==document.activeElement){Focus.register(instance)}});var Active=new DynamicPseudoClass("active",function(element){var instance=arguments;IE7.CSS.addEventHandler(element,"onmousedown",function(){Active.register(instance)})});addEventHandler(document,"onmouseup",function(){var instances=Active.instances;for(var i in instances){Active.unregister(instances[i])}});var Checked=new DynamicPseudoClass("checked",function(element){if(typeof element.checked!="boolean"){return}var instance=arguments;IE7.CSS.addEventHandler(element,"onpropertychange",function(){if(event.propertyName=="checked"){if(element.checked){Checked.register(instance)}else{Checked.unregister(instance)}}});if(element.checked){Checked.register(instance)}});var Enabled=new DynamicPseudoClass("enabled",function(element){if(typeof element.disabled!="boolean"){return}var instance=arguments;IE7.CSS.addEventHandler(element,"onpropertychange",function(){if(event.propertyName=="disabled"){if(!element.isDisabled){Enabled.register(instance)}else{Enabled.unregister(instance)}}});if(!element.isDisabled){Enabled.register(instance)}});var Disabled=new DynamicPseudoClass("disabled",function(element){if(typeof element.disabled!="boolean"){return}var instance=arguments;IE7.CSS.addEventHandler(element,"onpropertychange",function(){if(event.propertyName=="disabled"){if(element.isDisabled){Disabled.register(instance)}else{Disabled.unregister(instance)}}});if(element.isDisabled){Disabled.register(instance)}});var Indeterminate=new DynamicPseudoClass("indeterminate",function(element){if(typeof element.indeterminate!="boolean"){return}var instance=arguments;IE7.CSS.addEventHandler(element,"onpropertychange",function(){if(event.propertyName=="indeterminate"){if(element.indeterminate){Indeterminate.register(instance)}else{Indeterminate.unregister(instance)}}});IE7.CSS.addEventHandler(element,"onclick",function(){Indeterminate.unregister(instance)})});var Target=new DynamicPseudoClass("target",function(element){var instance=arguments;if(!element.tabIndex){element.tabIndex=0}IE7.CSS.addEventHandler(document,"onpropertychange",function(){if(event.propertyName=="activeElement"){if(element.id&&element.id==location.hash.slice(1)){Target.register(instance)}else{Target.unregister(instance)}}});if(element.id&&element.id==location.hash.slice(1)){Target.register(instance)}});var ATTR=/^attr/;var URL=/^url\s*\(\s*([^)]*)\)$/;var POSITION_MAP={before0:"beforeBegin",before1:"afterBegin",after0:"afterEnd",after1:"beforeEnd"};var PseudoElement=IE7.PseudoElement=Rule.extend({constructor:function(selector,position,cssText){this.position=position;var content=cssText.match(PseudoElement.CONTENT),match,entity;if(content){content=content[1];match=content.split(/\s+/);for(var i=0;(entity=match[i]);i++){match[i]=ATTR.test(entity)?{attr:entity.slice(5,-1)}:(entity.charAt(0)=="'")?getString(entity):decode(entity)}content=match}this.content=content;this.base(selector,decode(cssText))},init:function(){this.match=cssQuery(this.selector);for(var i=0;i%4",MATCH:/(.*):(before|after).*/,count:0});var UNSUCCESSFUL=/^(submit|reset|button)$/;IE7.HTML.addRecalc("button,input",function(button){if(button.tagName=="BUTTON"){var match=button.outerHTML.match(/ value="([^"]*)"/i);button.runtimeStyle.value=(match)?match[1]:""}if(button.type=="submit"){addEventHandler(button,"onclick",function(){button.runtimeStyle.clicked=true;setTimeout("document.all."+button.uniqueID+".runtimeStyle.clicked=false",1)})}});IE7.HTML.addRecalc("form",function(form){addEventHandler(form,"onsubmit",function(){for(var element,i=0;element=form[i];i++){if(UNSUCCESSFUL.test(element.type)&&!element.disabled&&!element.runtimeStyle.clicked){element.disabled=true;setTimeout("document.all."+element.uniqueID+".disabled=false",1)}else{if(element.tagName=="BUTTON"&&element.type=="submit"){setTimeout("document.all."+element.uniqueID+".value='"+element.value+"'",1);element.value=element.runtimeStyle.value}}}})});IE7.HTML.addRecalc("img",function(img){if(img.alt&&!img.title){img.title=""}});IE7.CSS.addRecalc("border-spacing",NUMERIC,function(element){if(element.currentStyle.borderCollapse!="collapse"){element.cellSpacing=getPixelValue(element,element.currentStyle["border-spacing"])}});IE7.CSS.addRecalc("box-sizing","content-box",IE7.Layout.boxSizing);IE7.CSS.addRecalc("box-sizing","border-box",IE7.Layout.borderBox);IE7.CSS.addFix(/opacity\s*:\s*([\d.]+)/,function(match,value){return"zoom:1;filter:Alpha(opacity="+((value*100)||1)+")"});var IMAGE=/^image/i;IE7.HTML.addRecalc("object",function(element){if(IMAGE.test(element.type)){element.body.style.cssText="margin:0;padding:0;border:none;overflow:hidden";return element}});IE7.loaded=true;(function(){try{documentElement.doScroll("left")}catch(e){setTimeout(arguments.callee,1);return}try{eval(script.innerHTML)}catch(e){}PNG=new RegExp(rescape(typeof IE7_PNG_SUFFIX=="string"?IE7_PNG_SUFFIX:"-trans.png")+"$","i");body=document.body;viewport=quirksMode?body:documentElement;body.className+=" ie7_body";documentElement.className+=" ie7_html";if(quirksMode){ie7Quirks()}IE7.CSS.init();IE7.HTML.init();IE7.HTML.apply();IE7.CSS.apply();IE7.recalc()})()})(); \ No newline at end of file diff --git a/static/scripts/packed/cookie_set.js b/static/scripts/packed/cookie_set.js index 4a7f3223cc8..7187d7205ca 100644 --- a/static/scripts/packed/cookie_set.js +++ b/static/scripts/packed/cookie_set.js @@ -1 +1 @@ -function CookieSet(A){this.cookie_name=A;this.store=store={};q.each((q.cookie(A)||"").split("|"),function(C,B){store[B]=true})}CookieSet.prototype.add=function(A){this.store[A]=true;return this};CookieSet.prototype.remove=function(A){delete this.store[A];return this};CookieSet.prototype.removeAll=function(A){this.store={};return this};CookieSet.prototype.contains=function(A){return(A in this.store)};CookieSet.prototype.save=function(){t=[];for(key in this.store){if(key!=""){t.push(key)}}q.cookie(this.cookie_name,t.join("|"));return this}; \ No newline at end of file +function CookieSet(a){this.cookie_name=a;this.store=store={};q.each((q.cookie(a)||"").split("|"),function(c,b){store[b]=true})}CookieSet.prototype.add=function(a){this.store[a]=true;return this};CookieSet.prototype.remove=function(a){delete this.store[a];return this};CookieSet.prototype.removeAll=function(a){this.store={};return this};CookieSet.prototype.contains=function(a){return(a in this.store)};CookieSet.prototype.save=function(){t=[];for(key in this.store){if(key!=""){t.push(key)}}q.cookie(this.cookie_name,t.join("|"));return this}; \ No newline at end of file diff --git a/static/scripts/packed/excanvas.js b/static/scripts/packed/excanvas.js index 8b787dce0cf..c24e123838c 100644 --- a/static/scripts/packed/excanvas.js +++ b/static/scripts/packed/excanvas.js @@ -1 +1 @@ -if(!window.CanvasRenderingContext2D){(function(){var L;var N={init:function(d){var e=d||document;e.createElement("canvas");if(/MSIE/.test(navigator.userAgent)&&!window.opera){var c=this;a();e.attachEvent("onreadystatechange",function(){c.init_(e)})}},init_:function(f){var e=f.createStyleSheet();e.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}canvas object{width:100%;height:100%;border:0;background:transparen;margin:0}";var d=f.getElementsByTagName("canvas");for(var c=0;c <\/script>');var c=document.scripts;var d=c[c.length-1];L=d.uniqueID;d.id=L}function C(c){return''}function H(){try{new ActiveXObject("AgControl.AgControl");return true}catch(c){return false}}var I=[];for(var X=0;X<16;X++){for(var W=0;W<16;W++){I[X*16+W]=X.toString(16)+W.toString(16)}}function T(){return[[1,0,0],[0,1,0],[0,0,1]]}function K(f,e){var d=T();for(var c=0;c<3;c++){for(var i=0;i<3;i++){var g=0;for(var h=0;h<3;h++){g+=f[c][h]*e[h][i]}d[c][i]=g}}return d}function J(c){Q(c,F(c),c.m_)}function Q(d,g,c){var f=g.renderTransform;var e;if(!f){f=P(d,"");e=P(d,"");f.matrix=e;g.renderTransform=f}else{e=f.matrix}e.m11=c[0][0];e.m12=c[0][1];e.m21=c[1][0];e.m22=c[1][1];e.offsetX=c[2][0];e.offsetY=c[2][1]}function V(d,c){c.fillStyle=d.fillStyle;c.lineCap=d.lineCap;c.lineJoin=d.lineJoin;c.lineWidth=d.lineWidth;c.miterLimit=d.miterLimit;c.shadowBlur=d.shadowBlur;c.shadowColor=d.shadowColor;c.shadowOffsetX=d.shadowOffsetX;c.shadowOffsetY=d.shadowOffsetY;c.strokeStyle=d.strokeStyle;c.globalAlpha=d.globalAlpha;c.arcScaleX_=d.arcScaleX_;c.arcScaleY_=d.arcScaleY_}function R(c){var f=/rgba\(([^)]+)\)/gi.exec(c);if(f){var e=f[1].split(",");return"#"+I[Math.floor(Number(e[3])*255)]+I[Number(e[0])]+I[Number(e[1])]+I[Number(e[2])]}var d=/rgb\(([^)]+)\)/gi.exec(c);if(d){var e=d[1].split(",");return"#FF"+I[Number(e[0])]+I[Number(e[1])]+I[Number(e[2])]}return c}function U(c){switch(c){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function F(c){return c.canvas.firstChild.content.findName("root")}function P(c,e,f){if(f){e=e.replace(/\%(\d+)/g,function(h,g){return f[Number(g)-1]})}try{return c.canvas.firstChild.content.createFromXaml(e)}catch(d){throw Error("Could not create XAML from: "+e)}}function E(c,f,g){var e=c.lastCanvas_||P(c,"");var d=P(c,f,g);e.children.add(d);Q(c,e,c.m_);if(!c.lastCanvas_){F(c).children.add(e);c.lastCanvas_=e}return d}function A(c,d){if(d instanceof Y){return d.createBrush_(c)}else{if(d instanceof M){throw Error("Not implemented")}else{return P(c,'',[R(d)])}}}function S(c){this.m_=T();this.lastCanvas_=null;this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=10;this.globalAlpha=1;this.canvas=c}var O=S.prototype;O.clearRect=function(){var c=F(this);c.children.clear();this.currentPath_=[];this.lastCanvas_=null};O.beginPath=function(){this.currentPath_=[]};O.moveTo=function(d,c){this.currentPath_.push("M"+d+","+c)};O.lineTo=function(d,c){if(this.currentPath_.length==0){return }this.currentPath_.push("L"+d+","+c)};O.bezierCurveTo=function(e,c,h,g,f,d){if(this.currentPath_.length==0){return }this.currentPath_.push("C"+e+","+c+" "+h+","+g+" "+f+" "+d)};O.quadraticCurveTo=function(f,e,d,c){if(this.currentPath_.length==0){return }this.currentPath_.push("Q"+f+","+e+" "+d+","+c)};O.arcTo=function(e,g,d,f,c){if(this.currentPath_.length==0){return }};O.arc=function(j,g,i,f,d,e){var c=Math.abs(f-d);if(f==d){return }var p=j+i*Math.cos(d);var o=g+i*Math.sin(d);if(c>=2*Math.PI){this.arc(j,g,i,f,f+Math.PI,e);this.arc(j,g,i,f+Math.PI,f+2*Math.PI,e);this.moveTo(p,o);return }var k=j+i*Math.cos(f);var h=g+i*Math.sin(f);var n=c*180/Math.PI;var l=e?0:1;var m=n>=180==Boolean(e)?0:1;if(this.currentPath_.length!=0){this.lineTo(k,h)}else{this.moveTo(k,h)}this.currentPath_.push("A"+i+","+i+" "+n+" "+m+" "+l+" "+p+","+o)};O.rect=function(e,d,c,f){this.moveTo(e,d);this.lineTo(e+c,d);this.lineTo(e+c,d+f);this.lineTo(e,d+f);this.closePath()};O.strokeRect=function(e,d,c,f){this.beginPath();this.moveTo(e,d);this.lineTo(e+c,d);this.lineTo(e+c,d+f);this.lineTo(e,d+f);this.closePath();this.stroke();this.currentPath_=[]};O.fillRect=function(e,d,c,f){this.beginPath();this.moveTo(e,d);this.lineTo(e+c,d);this.lineTo(e+c,d+f);this.lineTo(e,d+f);this.closePath();this.fill();this.currentPath_=[]};O.createLinearGradient=function(d,f,c,e){return new D(d,f,c,e)};O.createRadialGradient=function(f,h,e,d,g,c){return new G(f,h,e,d,g,c)};O.drawImage=function(e,n){var q,p,c,i,k,j,l,h;if(arguments.length==3){q=arguments[1];p=arguments[2]}else{if(arguments.length==5){q=arguments[1];p=arguments[2];c=arguments[3];i=arguments[4]}else{if(arguments.length==9){k=arguments[1];j=arguments[2];l=arguments[3];h=arguments[4];q=arguments[5];p=arguments[6];c=arguments[7];i=arguments[8]}else{throw Error("Invalid number of arguments")}}}var g;if(arguments.length==9){g=E(this,'',[e.src]);var f=P(this,'',[k,j,l,h]);g.clip=f;var d=T();d[2][0]=-k;d[2][1]=-j;var o=T();o[0][0]=c/l;o[1][1]=i/h;d=K(d,o);d[2][0]+=q;d[2][1]+=p;Q(this,g,d)}else{g=E(this,'',[e.src,q,p]);if(c!=undefined||i!=undefined){g.width=c;g.height=i;g.stretch="fill"}}};O.stroke=function(){if(this.currentPath_.length==0){return }var c=E(this,'',[this.currentPath_.join(" ")]);c.stroke=A(this,this.strokeStyle);c.opacity=this.globalAlpha;c.strokeThickness=this.lineWidth;c.strokeMiterLimit=this.miterLimit;c.strokeLineJoin=this.lineJoin;c.strokeEndLineCap=c.strokeStartLineCap=U(this.lineCap)};O.fill=function(){if(this.currentPath_.length==0){return }var c=E(this,'',[this.currentPath_.join(" ")]);c.data.fillRule="NonZero";c.fill=A(this,this.fillStyle)};O.closePath=function(){this.currentPath_.push("z")};function Z(d,c){d.m_=c;d.lastCanvas_=null}O.save=function(){var c={};V(this,c);this.aStack_.push(c);this.mStack_.push(this.m_);Z(this,K(T(),this.m_))};O.restore=function(){V(this.aStack_.pop(),this);Z(this,this.mStack_.pop())};O.translate=function(e,d){var c=[[1,0,0],[0,1,0],[e,d,1]];Z(this,K(c,this.m_))};O.rotate=function(e){var g=Math.cos(e);var f=Math.sin(e);var d=[[g,f,0],[-f,g,0],[0,0,1]];Z(this,K(d,this.m_))};O.scale=function(e,d){var c=[[e,0,0],[0,d,0],[0,0,1]];Z(this,K(c,this.m_))};O.transform=function(g,f,i,h,d,c){var e=[[g,f,0],[i,h,0],[d,c,1]];Z(this,K(e,this.m_))};O.setTransform=function(f,e,h,g,d,c){Z(this,[[f,e,0],[h,g,0],[d,c,1],])};O.clip=function(){};O.createPattern=function(){return new M};function Y(){this.colors_=[]}Y.prototype.addColorStop=function(d,c){c=R(c);this.colors_.push({offset:d,color:c})};Y.prototype.createStops_=function(e,g,d){var j=g.gradientStops;for(var h=0,k;k=d[h];h++){var f=R(k.color);j.add(P(e,'',[f,k.offset]))}};function D(d,f,c,e){Y.call(this);this.x0_=d;this.y0_=f;this.x1_=c;this.y1_=e}D.prototype=new Y;D.prototype.createBrush_=function(c){var d=P(c,'',[this.x0_,this.y0_,this.x1_,this.y1_]);this.createStops_(c,d,this.colors_);return d};function B(c){return isNaN(c)||!isFinite(c)}function G(g,i,f,e,h,c){if(f<0||c<0||B(g)||B(i)||B(e)||B(h)){var d=Error("DOMException.INDEX_SIZE_ERR");d.code=1;throw d}Y.call(this);this.x0_=g;this.y0_=i;this.r0_=f;this.x1_=e;this.y1_=h;this.r1_=c}G.prototype=new Y;Y.prototype.createBrush_=function(f){if(this.x0_==this.x1_&&this.y0_==this.y1_&&this.r0_==this.r1_){return null}var d=Math.max(this.r0_,this.r1_);var j=Math.min(this.r0_,this.r1_);var g=P(f,'',[this.x0_,this.y0_,this.x1_,this.y1_,d]);var e=this.colors_.concat();if(this.r1_0){for(var h=0,k;k=e[h];h++){k.offset=j/d+(d-j)/d*k.offset}}this.createStops_(f,g,e);return g};function M(){}G_vmlCanvasManager=N;CanvasRenderingContext2D=S;CanvasGradient=Y;CanvasPattern=M})()}; \ No newline at end of file +if(!window.CanvasRenderingContext2D){(function(){var n;var p={init:function(j){var E=j||document;E.createElement("canvas");if(/MSIE/.test(navigator.userAgent)&&!window.opera){var i=this;C();E.attachEvent("onreadystatechange",function(){i.init_(E)})}},init_:function(G){var F=G.createStyleSheet();F.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}canvas object{width:100%;height:100%;border:0;background:transparen;margin:0}";var E=G.getElementsByTagName("canvas");for(var j=0;j <\/script>');var i=document.scripts;var j=i[i.length-1];n=j.uniqueID;j.id=n}function c(i){return''}function h(){try{new ActiveXObject("AgControl.AgControl");return true}catch(i){return false}}var k=[];for(var z=0;z<16;z++){for(var y=0;y<16;y++){k[z*16+y]=z.toString(16)+y.toString(16)}}function v(){return[[1,0,0],[0,1,0],[0,0,1]]}function m(F,E){var j=v();for(var i=0;i<3;i++){for(var I=0;I<3;I++){var G=0;for(var H=0;H<3;H++){G+=F[i][H]*E[H][I]}j[i][I]=G}}return j}function l(i){s(i,f(i),i.m_)}function s(j,G,i){var F=G.renderTransform;var E;if(!F){F=r(j,"");E=r(j,"");F.matrix=E;G.renderTransform=F}else{E=F.matrix}E.m11=i[0][0];E.m12=i[0][1];E.m21=i[1][0];E.m22=i[1][1];E.offsetX=i[2][0];E.offsetY=i[2][1]}function x(j,i){i.fillStyle=j.fillStyle;i.lineCap=j.lineCap;i.lineJoin=j.lineJoin;i.lineWidth=j.lineWidth;i.miterLimit=j.miterLimit;i.shadowBlur=j.shadowBlur;i.shadowColor=j.shadowColor;i.shadowOffsetX=j.shadowOffsetX;i.shadowOffsetY=j.shadowOffsetY;i.strokeStyle=j.strokeStyle;i.globalAlpha=j.globalAlpha;i.arcScaleX_=j.arcScaleX_;i.arcScaleY_=j.arcScaleY_}function t(i){var F=/rgba\(([^)]+)\)/gi.exec(i);if(F){var E=F[1].split(",");return"#"+k[Math.floor(Number(E[3])*255)]+k[Number(E[0])]+k[Number(E[1])]+k[Number(E[2])]}var j=/rgb\(([^)]+)\)/gi.exec(i);if(j){var E=j[1].split(",");return"#FF"+k[Number(E[0])]+k[Number(E[1])]+k[Number(E[2])]}return i}function w(i){switch(i){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function f(i){return i.canvas.firstChild.content.findName("root")}function r(i,E,F){if(F){E=E.replace(/\%(\d+)/g,function(H,G){return F[Number(G)-1]})}try{return i.canvas.firstChild.content.createFromXaml(E)}catch(j){throw Error("Could not create XAML from: "+E)}}function e(i,F,G){var E=i.lastCanvas_||r(i,"");var j=r(i,F,G);E.children.add(j);s(i,E,i.m_);if(!i.lastCanvas_){f(i).children.add(E);i.lastCanvas_=E}return j}function a(i,j){if(j instanceof A){return j.createBrush_(i)}else{if(j instanceof o){throw Error("Not implemented")}else{return r(i,'',[t(j)])}}}function u(i){this.m_=v();this.lastCanvas_=null;this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=10;this.globalAlpha=1;this.canvas=i}var q=u.prototype;q.clearRect=function(){var i=f(this);i.children.clear();this.currentPath_=[];this.lastCanvas_=null};q.beginPath=function(){this.currentPath_=[]};q.moveTo=function(j,i){this.currentPath_.push("M"+j+","+i)};q.lineTo=function(j,i){if(this.currentPath_.length==0){return}this.currentPath_.push("L"+j+","+i)};q.bezierCurveTo=function(E,i,H,G,F,j){if(this.currentPath_.length==0){return}this.currentPath_.push("C"+E+","+i+" "+H+","+G+" "+F+" "+j)};q.quadraticCurveTo=function(F,E,j,i){if(this.currentPath_.length==0){return}this.currentPath_.push("Q"+F+","+E+" "+j+","+i)};q.arcTo=function(E,G,j,F,i){if(this.currentPath_.length==0){return}};q.arc=function(J,G,I,F,j,E){var i=Math.abs(F-j);if(F==j){return}var P=J+I*Math.cos(j);var O=G+I*Math.sin(j);if(i>=2*Math.PI){this.arc(J,G,I,F,F+Math.PI,E);this.arc(J,G,I,F+Math.PI,F+2*Math.PI,E);this.moveTo(P,O);return}var K=J+I*Math.cos(F);var H=G+I*Math.sin(F);var N=i*180/Math.PI;var L=E?0:1;var M=N>=180==Boolean(E)?0:1;if(this.currentPath_.length!=0){this.lineTo(K,H)}else{this.moveTo(K,H)}this.currentPath_.push("A"+I+","+I+" "+N+" "+M+" "+L+" "+P+","+O)};q.rect=function(E,j,i,F){this.moveTo(E,j);this.lineTo(E+i,j);this.lineTo(E+i,j+F);this.lineTo(E,j+F);this.closePath()};q.strokeRect=function(E,j,i,F){this.beginPath();this.moveTo(E,j);this.lineTo(E+i,j);this.lineTo(E+i,j+F);this.lineTo(E,j+F);this.closePath();this.stroke();this.currentPath_=[]};q.fillRect=function(E,j,i,F){this.beginPath();this.moveTo(E,j);this.lineTo(E+i,j);this.lineTo(E+i,j+F);this.lineTo(E,j+F);this.closePath();this.fill();this.currentPath_=[]};q.createLinearGradient=function(j,F,i,E){return new d(j,F,i,E)};q.createRadialGradient=function(F,H,E,j,G,i){return new g(F,H,E,j,G,i)};q.drawImage=function(E,M){var P,O,i,I,K,J,L,H;if(arguments.length==3){P=arguments[1];O=arguments[2]}else{if(arguments.length==5){P=arguments[1];O=arguments[2];i=arguments[3];I=arguments[4]}else{if(arguments.length==9){K=arguments[1];J=arguments[2];L=arguments[3];H=arguments[4];P=arguments[5];O=arguments[6];i=arguments[7];I=arguments[8]}else{throw Error("Invalid number of arguments")}}}var G;if(arguments.length==9){G=e(this,'',[E.src]);var F=r(this,'',[K,J,L,H]);G.clip=F;var j=v();j[2][0]=-K;j[2][1]=-J;var N=v();N[0][0]=i/L;N[1][1]=I/H;j=m(j,N);j[2][0]+=P;j[2][1]+=O;s(this,G,j)}else{G=e(this,'',[E.src,P,O]);if(i!=undefined||I!=undefined){G.width=i;G.height=I;G.stretch="fill"}}};q.stroke=function(){if(this.currentPath_.length==0){return}var i=e(this,'',[this.currentPath_.join(" ")]);i.stroke=a(this,this.strokeStyle);i.opacity=this.globalAlpha;i.strokeThickness=this.lineWidth;i.strokeMiterLimit=this.miterLimit;i.strokeLineJoin=this.lineJoin;i.strokeEndLineCap=i.strokeStartLineCap=w(this.lineCap)};q.fill=function(){if(this.currentPath_.length==0){return}var i=e(this,'',[this.currentPath_.join(" ")]);i.data.fillRule="NonZero";i.fill=a(this,this.fillStyle)};q.closePath=function(){this.currentPath_.push("z")};function B(j,i){j.m_=i;j.lastCanvas_=null}q.save=function(){var i={};x(this,i);this.aStack_.push(i);this.mStack_.push(this.m_);B(this,m(v(),this.m_))};q.restore=function(){x(this.aStack_.pop(),this);B(this,this.mStack_.pop())};q.translate=function(E,j){var i=[[1,0,0],[0,1,0],[E,j,1]];B(this,m(i,this.m_))};q.rotate=function(j){var F=Math.cos(j);var E=Math.sin(j);var i=[[F,E,0],[-E,F,0],[0,0,1]];B(this,m(i,this.m_))};q.scale=function(E,j){var i=[[E,0,0],[0,j,0],[0,0,1]];B(this,m(i,this.m_))};q.transform=function(G,F,I,H,j,i){var E=[[G,F,0],[I,H,0],[j,i,1]];B(this,m(E,this.m_))};q.setTransform=function(F,E,H,G,j,i){B(this,[[F,E,0],[H,G,0],[j,i,1],])};q.clip=function(){};q.createPattern=function(){return new o};function A(){this.colors_=[]}A.prototype.addColorStop=function(j,i){i=t(i);this.colors_.push({offset:j,color:i})};A.prototype.createStops_=function(E,G,j){var I=G.gradientStops;for(var H=0,J;J=j[H];H++){var F=t(J.color);I.add(r(E,'',[F,J.offset]))}};function d(j,F,i,E){A.call(this);this.x0_=j;this.y0_=F;this.x1_=i;this.y1_=E}d.prototype=new A;d.prototype.createBrush_=function(i){var j=r(i,'',[this.x0_,this.y0_,this.x1_,this.y1_]);this.createStops_(i,j,this.colors_);return j};function b(i){return isNaN(i)||!isFinite(i)}function g(G,I,F,E,H,i){if(F<0||i<0||b(G)||b(I)||b(E)||b(H)){var j=Error("DOMException.INDEX_SIZE_ERR");j.code=1;throw j}A.call(this);this.x0_=G;this.y0_=I;this.r0_=F;this.x1_=E;this.y1_=H;this.r1_=i}g.prototype=new A;A.prototype.createBrush_=function(F){if(this.x0_==this.x1_&&this.y0_==this.y1_&&this.r0_==this.r1_){return null}var j=Math.max(this.r0_,this.r1_);var I=Math.min(this.r0_,this.r1_);var G=r(F,'',[this.x0_,this.y0_,this.x1_,this.y1_,j]);var E=this.colors_.concat();if(this.r1_0){for(var H=0,J;J=E[H];H++){J.offset=I/j+(j-I)/j*J.offset}}this.createStops_(F,G,E);return G};function o(){}G_vmlCanvasManager=p;CanvasRenderingContext2D=u;CanvasGradient=A;CanvasPattern=o})()}; \ No newline at end of file diff --git a/static/scripts/packed/galaxy.base.js b/static/scripts/packed/galaxy.base.js index bfd39606e37..9866a50b4bc 100644 --- a/static/scripts/packed/galaxy.base.js +++ b/static/scripts/packed/galaxy.base.js @@ -1 +1 @@ -jQuery(document).ready(function(){jQuery("a[@confirm]").click(function(){return confirm(jQuery(this).attr("confirm"))});jQuery("div[@popupmenu]").each(function(){var B={};$(this).find("a").each(function(){var C=$(this).attr("confirm"),D=$(this).attr("href"),E=$(this).attr("target");B[$(this).text()]=function(){if(!C||confirm(C)){var F=window;if(E=="_parent"){F=window.parent}F.location=D}}});var A=$("#"+$(this).attr("popupmenu"));make_popupmenu(A,B);$(this).remove();A.show()})});function ensure_popup_helper(){if($("#popup-helper").length==0){$("