def run(self):
        appdef = None
        projFile = QgsProject.instance().fileName()
        if projFile:
            appdefFile =  projFile + ".appdef"
            if os.path.exists(appdefFile):
                if pluginSetting("askreload") == "Ask":
                    ret = QMessageBox.question(self.iface.mainWindow(), "Web app builder",
                                              "This project has been already published as a web app.\n"
                                              "Do you want to reload app configuration?",
                                              QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
                    if ret == QMessageBox.Yes:
                        appdef = loadAppdef(appdefFile)
                elif pluginSetting("askreload") == "Open last configuration":
                    appdef = loadAppdef(appdefFile)
        initialize()
        # reset credential token in case related credentials are changed
        utils.resetCachedToken()
        try:
            dlg = MainDialog(appdef)
            dlg.exec_()
        except:
            dlg.progressBar.setMaximum(100)
            dlg.progressBar.setValue(0)
            dlg.progressBar.setVisible(False)
            dlg.progressLabel.setVisible(False)
            QApplication.restoreOverrideCursor()

            QgsMessageLog.logMessage(traceback.format_exc(), "WebAppBuilder", level=QgsMessageLog.CRITICAL)
            QMessageBox.critical(self.iface.mainWindow(), "Unmanaged error. See QGIS log for more details.")
def getToken():
    """
    Function to get a access token from endpoint sending "custom" basic auth.
    Parameters

    The return value is a token string or Exception. This is cached and returned
    every call or request again if cache is empty
    """
    global __cachedToken
    if __cachedToken:
        return __cachedToken

    # start with a clean cache
    __cachedToken = None

    # get authcfg to point to saved credentials in QGIS Auth manager
    authcfg = getConnectAuthCfg()
    if not authcfg:
        raise Exception("Connect authcfg is empty")

    usr, pwd = getCredentialsFromAuthDb(authcfg)
    if not usr and not pwd:
        raise Exception(
            "Cannot find stored credentials with authcfg = {}".format(authcfg))

    # prepare data for the token request
    httpAuth = base64.b64encode('{}:{}'.format(usr.strip(),
                                               pwd.strip())).decode("ascii")
    headers = {}
    headers["Authorization"] = "Basic {}".format(httpAuth)
    headers["Content-Type"] = "application/json"

    # request token in synchronous way => block GUI
    nam = NetworkAccessManager(debug=pluginSetting("logresponse"))
    try:
        res, resText = nam.request(authUrl(), method="GET", headers=headers)
    except Exception as e:
        if nam.http_call_result.status_code in [401, 403]:
            raise Exception(
                "Permission denied with current Connect credentials")
        else:
            raise e

    # todo: check res code in case not authorization
    if not res.ok:
        raise Exception("Cannot get token: {}".format(res.reason))

    # parse token from resText
    resDict = json.loads(str(resText))
    try:
        __cachedToken = resDict["token"]
    except:
        pass

    if not __cachedToken:
        raise Exception("Cannot get authentication token")

    return __cachedToken
def appSDKification(folder, progress):
    ''' zip app folder and send to WAB compiler to apply SDK compilation.
    The returned zip will be the official webapp
    '''
    progress.oscillate()

    progress.setText("Get Authorization token")
    try:
        global __appSDKification_doAgain
        if __appSDKification_doAgain:
            QgsMessageLog.logMessage("Renew token in case of it is expired and retry", level=QgsMessageLog.WARNING)
            utils.resetCachedToken()
        token = utils.getToken()
    except Exception as e:
        pub.sendMessage(utils.topics.endAppSDKification, success=False, reason=str(e))
        return

    # zip folder to send for compiling
    progress.setText("Preparing data to compile")
    zipFileName = tempFilenameInTempFolder("webapp.zip", "webappbuilder")
    try:
        with zipfile.ZipFile(zipFileName, "w") as zf:
            relativeFrom = os.path.dirname(folder)
            for dirname, subdirs, files in os.walk(folder):
                # exclude data folder
                if 'data' in subdirs:
                    subdirs.remove('data')
                if relativeFrom in dirname:
                    zf.write(dirname, dirname[len(relativeFrom):])
                for filename in files:
                    fiename = os.path.join(dirname, filename)
                    zf.write(fiename, fiename[len(relativeFrom):])
    except:
        msg = "Could not zip webapp folder: {}".format(folder)
        pub.sendMessage(utils.topics.endAppSDKification, success=False, reason=msg)
        return

    # prepare data for WAB compiling request
    with open(zipFileName, 'rb') as f:
        fileContent = f.read()
    fields = { 'file': (os.path.basename(zipFileName), fileContent) }
    payload, content_type = encode_multipart_formdata(fields)

    headers = {}
    headers["authorization"] = "Bearer {}".format(token)
    headers["Content-Type"] = content_type

    # prepare request (as in NetworkAccessManager) but without blocking request
    # do http post
    progress.setText("Wait compilation")

    global __anam
    if __anam:
        del __anam
        __anam = None
    __anam = AsyncNetworkAccessManager(debug=pluginSetting("logresponse"))
    __anam.request(utils.wabCompilerUrl(), method='POST', body=payload, headers=headers, blocking=False)
    __anam.reply.finished.connect( lambda: manageFinished(__anam, zipFileName, folder, progress) )
 def zoomTo(self):
     apikey = pluginSetting("apiKey")
     if apikey is None or apikey == "":
         self._showMessage(
             'what3words API key is not set. Please set it and try again.',
             QgsMessageBar.WARNING)
         return
     self.zoomToDialog.setApiKey(apikey)
     self.zoomToDialog.show()
    def getLegendSymbols(self, layer, ilayer, legendFolder):
        size = self._parameters["size"]
        qsize = QSize(size, size)
        symbols = []

        def appendSymbol(title, href):
            symbols.append({'title': title, 'href': href})

        if layer.type() == layer.VectorLayer:
            renderer = layer.rendererV2()
            if isinstance(renderer, QgsSingleSymbolRendererV2):
                img = renderer.symbol().asImage(qsize)
                symbolPath = os.path.join(legendFolder, "%i_0.png" % (ilayer))
                img.save(symbolPath)
                appendSymbol("", os.path.basename(symbolPath))
            elif isinstance(renderer, QgsCategorizedSymbolRendererV2):
                for isymbol, cat in enumerate(renderer.categories()):
                    img = cat.symbol().asImage(qsize)
                    symbolPath = os.path.join(legendFolder,
                                              "%i_%i.png" % (ilayer, isymbol))
                    img.save(symbolPath)
                    appendSymbol(cat.label(), os.path.basename(symbolPath))
            elif isinstance(renderer, QgsGraduatedSymbolRendererV2):
                for isymbol, ran in enumerate(renderer.ranges()):
                    img = ran.symbol().asImage(qsize)
                    symbolPath = os.path.join(legendFolder,
                                              "%i_%i.png" % (ilayer, isymbol))
                    img.save(symbolPath)
                    appendSymbol(
                        "%s-%s" % (ran.lowerValue(), ran.upperValue()),
                        os.path.basename(symbolPath))
            elif isinstance(renderer, QgsRuleBasedRendererV2):
                for isymbol, rule in enumerate(renderer.rootRule().children()):
                    img = rule.symbol().asImage(qsize)
                    symbolPath = os.path.join(legendFolder,
                                              "%i_%i.png" % (ilayer, isymbol))
                    img.save(symbolPath)
                    appendSymbol(rule.label(), os.path.basename(symbolPath))
        elif layer.type() == layer.RasterLayer:
            if layer.providerType() == "wms":
                source = layer.source()
                layerName = re.search(r"layers=(.*?)(?:&|$)",
                                      source).groups(0)[0]
                url = re.search(r"url=(.*?)(?:&|$)", source).groups(0)[0]
                styles = re.search(r"styles=(.*?)(?:&|$)", source).groups(0)[0]
                fullUrl = (
                    "%s?LAYER=%s&STYLES=%s&REQUEST=GetLegendGraphic&VERSION=1.0.0&FORMAT=image/png&WIDTH=%i&HEIGHT=%i"
                    % (url, layerName, styles, size, size))
                nam = NetworkAccessManager(debug=pluginSetting("logresponse"))
                response, content = nam.request(fullUrl)
                symbolPath = os.path.join(legendFolder, "%i_0.png" % ilayer)
                with open(symbolPath, 'wb') as f:
                    f.write(content)
                appendSymbol("", os.path.basename(symbolPath))
        return symbols
    def processAlgorithm(self, progress):
        '''
        Here is where the algorithm functionality takes places. This method is 
        called when the algorithm is executed'''

        apik = pluginSetting("apiKey")
        if apik is None or apik == ""::
            '''
            When there is a problem in a Processing algorithm, a 
            GeoAlgorithmExecutionException should be raised.
            '''
             raise GeoAlgorithmExecutionException("what3words API key is not defined")
    def setTool(self):
        apikey = pluginSetting("apiKey")
        if apikey is None or apikey == "":
            self._showMessage(
                'what3words API key is not set. Please set it and try again.',
                QgsMessageBar.WARNING)
            return

        #Create the map tool if needed
        if self.mapTool is None:
            self.mapTool = W3WMapTool(iface.mapCanvas())
        #Change the menu item so it shows that the tool is active
        self.toolAction.setChecked(True)
        #Set the tool as the active map tool
        iface.mapCanvas().setMapTool(self.mapTool)
    def endCreateAppListener(self, success, reason):
        self.onCreatingApp = False

        # reset button status and cursor
        self.buttonCreateOrStopApp.setText(self.createAppButtonText)
        QApplication.restoreOverrideCursor()

        from pubsub import pub
        pub.unsubscribe(self.endCreateAppListener, utils.topics.endFunction)
        if success:
            if pluginSetting("compileinserver"):
                QMessageBox.information(
                    self, self.tr("Web app"),
                    self.tr("Web app was correctly created and built."))
            else:
                QMessageBox.information(
                    self, self.tr("Web app"),
                    self.
                    tr("Web app file were correctly created.\n"
                       "A web app can be built from them using Boundless WebSDK"
                       ))
        elif reason:
            QgsMessageLog.logMessage("WebAppBuilder: {}".format(reason),
                                     level=QgsMessageLog.CRITICAL)
            if 'Request cancelled by user' in reason:
                # do nothing
                pass
            elif 'Cannot post preview webapp: Network error #5: Operation canceled' in reason:
                QMessageBox.critical(
                    self, self.tr("Error creating web app"),
                    self.
                    tr("Network error due to a timeout.\n"
                       "Please configure a longer timeout going to:\n"
                       "Settings->Options->Network->Timeout for network requests (ms)."
                       ))
            elif 'Permission denied' in reason:
                QMessageBox.critical(
                    self, self.tr("Error creating web app"),
                    self.tr(
                        "Permission denied with current Connect credentials"))
            else:
                QMessageBox.critical(
                    self, self.tr("Error creating web app"),
                    self.
                    tr("Could not create web app.\nCheck the QGIS log for more details."
                       ))
    def createOrStopApp(self):
        # check if app is compiling
        if self.onCreatingApp:
            stopAppCreation()
            return

        # start compilation
        try:
            appdef = self.createAppDefinition()
            problems = checkAppCanBeCreated(appdef)
            if pluginSetting("compileinserver"):
                QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
                try:
                    checkSDKServerVersion()
                except VersionMismatchError, e:
                    problems.append(str(e))
                except Exception, e:
                    QApplication.restoreOverrideCursor()
                    QMessageBox.warning(self, "Problem checking SDK version",
                                        str(e), QMessageBox.Close)
                    return
def checkSDKServerVersion():
    localVersion = utils.sdkVersion()

    token = utils.getToken()

    headers = {}
    headers["authorization"] = "Bearer {}".format(token)

    nam = NetworkAccessManager(debug=pluginSetting("logresponse"))
    try:
        resp, text = nam.request(wabVersionUrl(), headers=headers)
    except Exception as e:
        # check if 401/403 => probably token expired
        permissionDenied = utils.isPermissionDenied(str(e))
        if not permissionDenied:
            raise e
        else:
            # renew token and try again
            utils.resetCachedToken()
            token = utils.getToken()

            # retry call
            headers["authorization"] = "Bearer {}".format(token)
            try:
                resp, text = nam.request(wabVersionUrl(), headers=headers)
            except Exception as e:
                # check if 401/403 => probably token expired
                permissionDenied = utils.isPermissionDenied(str(e))
                if not permissionDenied:
                    raise e
                else:
                    raise Exception(
                        "Permission denied with current Connect credentials")

    remoteVersion = json.loads(text)["boundless-sdk"]
    if localVersion != remoteVersion:
        raise VersionMismatchError(
            "The server SDK version (%s) is different from the expected version (%s)"
            % (remoteVersion, localVersion))
                except Exception as ex:
                    errMessage = str(ex)
                    QMessageBox.warning(self, "Need Connect credentials",
                                        errMessage, QMessageBox.Close)
                    return
            if problems:
                dlg = AppDefProblemsDialog(problems)
                dlg.exec_()
                if not dlg.ok:
                    return
            # now ask where to store app
            folder = askForFolder(self, "Select folder to store app")
            if folder:
                if os.path.exists(os.path.join(
                        folder,
                        "webapp")) and pluginSetting("overwritewarning"):
                    ret = QMessageBox.warning(
                        self, "Output folder",
                        " The selected folder already contains a 'webapp' subfolder.\n"
                        "Do you confirm that you want to overwrite it?",
                        QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                    if ret == QMessageBox.No:
                        return

                # set buttons status
                self.setButtonsEnabled(
                    status=False, excludeList=[self.buttonCreateOrStopApp])
                self.createAppButtonText = self.buttonCreateOrStopApp.text()
                self.buttonCreateOrStopApp.setText(self.tr("Stop"))

                try:
Exemple #12
0
def wabVersionUrl():
    return urllib.parse.unquote(
        pluginSetting("sdkendpoint").rstrip("/") + "/version")
Exemple #13
0
def wabCompilerUrl():
    return urllib.parse.unquote(
        pluginSetting("sdkendpoint").rstrip("/") + "/package")
def authUrl():
    return urllib.parse.unquote(pluginSetting("tokenendpoint"))
def writeWebApp(appdef, folder, forPreview, progress):
    """WriteApp end is notified using
    pub.sendMessage(utils.topics.endWriteWebApp, success=[True, False], reason=[str|None])
    """
    progress.setText("Copying resources files")
    dst = os.path.join(folder, "webapp")
    if os.path.exists(dst):
        shutil.rmtree(dst)
    QDir().mkpath(dst)
    sdkFolder = os.path.join(os.path.dirname(__file__), "websdk_full")
    if forPreview:
        shutil.copy(os.path.join(sdkFolder, "full-debug.js"), dst)

    QDir().mkpath(os.path.join(dst, "data"))

    jsFolder = os.path.join(os.path.dirname(__file__), "js")
    jsDstFolder = os.path.join(dst, "resources","js")
    shutil.copytree(jsFolder, jsDstFolder)
    cssFolder = os.path.join(os.path.dirname(__file__), "css")
    cssDstFolder = os.path.join(dst, "resources","css")
    shutil.copytree(cssFolder, cssDstFolder)
    shutil.copy(os.path.join(sdkFolder, "ol.css"), cssDstFolder)
    layers = appdef["Layers"]
    exportLayers(layers, dst, progress,
                 appdef["Settings"]["Precision for GeoJSON export"],
                 appdef["Settings"]["App view CRS"], forPreview)

    class App():
        tabs = []
        ol3controls = []
        tools = []
        panels = []
        mappanels = []
        variables = []
        scripts = []
        scriptsbody = []
        posttarget = []
        imports = []
        aftermap = []
        def newInstance(self):
            _app = App()
            _app.tabs = list(self.tabs)
            _app.ol3controls = list(self.ol3controls)
            _app.tools = list(self.tools)
            _app.panels = list(self.panels)
            _app.aftermap = list(self.aftermap)
            _app.mappanels = list(self.mappanels)
            _app.variables = list(self.variables)
            _app.scripts = list(self.scripts)
            _app.scriptsbody = list(self.scriptsbody)
            _app.posttarget = list(self.posttarget)
            _app.imports = list(self.imports)
            return _app

    _app = App()
    exportStyles(layers, dst, appdef["Settings"], "timeline" in appdef["Widgets"], _app, progress)
    writeLayersAndGroups(appdef, dst, _app, forPreview, progress)

    widgets = sorted(appdef["Widgets"].values(), key=attrgetter('order'))
    for w in widgets:
        w.write(appdef, dst, _app, progress)

    writeCss(appdef, dst, widgets)

    baseTarget = "_self" if appdef["Settings"]["Open hyperlinks in"] == 0 else "_blank"
    _app.scripts.append("<base target='%s'>" % baseTarget)

    if forPreview:
        app = _app.newInstance()
        writeJs(appdef, dst, app, progress)
        app.scriptsbody.extend(['<script src="full-debug.js"></script>',
                                '<script src="app_prebuilt.js"></script>'])
        for layer in appdef["Layers"]:
            if layer.layer.type() == layer.layer.VectorLayer:
                app.scriptsbody.append('<script src="./data/lyr_%s.js"></script>' % safeName(layer.layer.name()))
        writeHtml(appdef, dst, app, progress, "index_debug.html")
        pub.sendMessage(utils.topics.endWriteWebApp, success=True, reason=None)

    else:
        app = _app.newInstance()
        writeJsx(appdef, dst, app, progress)

        app = _app.newInstance()
        app.scriptsbody.extend(['<script src="/loader.js"></script>',
                                '<script src="/build/app-debug.js"></script>'])
        writeHtml(appdef, dst, app, progress, "index.html")

        if pluginSetting("compileinserver"):
            # apply SDK compilation to the saved webapp
            pub.subscribe(endAppSDKificationListener, utils.topics.endAppSDKification)
            try:
                global __appSDKification_doAgain
                __appSDKification_doAgain = False
                appSDKification(dst, progress)
            except Exception as e:
                pub.sendMessage(utils.topics.endAppSDKification, success=False, reason=str(e))
        else:
            pub.sendMessage(utils.topics.endWriteWebApp, success=True, reason=None)
Exemple #16
0
def _setWrongSdkEndpoint():
    global _sdkEndpoint
    _sdkEndpoint = pluginSetting("sdkendpoint")
    setPluginSetting("sdkendpoint", "wrong")
Exemple #17
0
 def __init__(self, canvas):
     QgsMapTool.__init__(self, canvas)
     self.setCursor(Qt.CrossCursor)
     apiKey = pluginSetting("apiKey")
     self.w3w = what3words(apikey=apiKey)
def checkAppCanBeCreated(appdef, forPreview=False):
    ##viewCrs = appdef["Settings"]["App view CRS"]
    jsonp = appdef["Settings"]["Use JSONP for WFS connections"]
    problems = []
    layers = appdef["Layers"]

    widgets = appdef["Widgets"].values()
    for w in widgets:
        w.checkProblems(appdef, problems, forPreview)

    themeModule = importlib.import_module("webappbuilder.themes." +
                                          appdef["Settings"]["Theme"])
    themeModule.checkProblems(appdef, problems)

    def getSize(lyr):
        ptsInFeature = 1 if lyr.geometryType(
        ) == QGis.Point else 10  #quick estimate...
        return lyr.featureCount() * (ptsInFeature + lyr.pendingFields().size())

    MAXSIZE = 30000
    for applayer in layers:
        if applayer.layer.type(
        ) == applayer.layer.VectorLayer and applayer.layer.providerType(
        ).lower() != "wfs":
            if getSize(applayer.layer) > MAXSIZE:
                problems.append(
                    "Layer %s might be too big for being loaded directly from a file."
                    % applayer.layer.name())

    nam = NetworkAccessManager(debug=pluginSetting("logresponse"))
    for applayer in layers:
        layer = applayer.layer
        if layer.providerType().lower() == "wms":
            try:
                source = layer.source()
                url = re.search(
                    r"url=(.*?)(?:&|$)",
                    source).groups(0)[0] + "?REQUEST=GetCapabilities"
                r, content = run(
                    lambda: nam.request(url, headers={"origin": "null"}))
                cors = r.headers.get("Access-Control-Allow-Origin", "").lower()
                if cors not in ["null", "*"]:
                    problems.append(
                        "Server for layer %s is not allowed to accept cross-origin requests."
                        " Popups and printing might not work correctly for that layer."
                        % layer.name())
            except:
                QgsMessageLog.logMessage(
                    "Warning: cannot verify cross-origin configuration for layer '%s'."
                    % layer.name(),
                    level=QgsMessageLog.WARNING)

    for applayer in layers:
        layer = applayer.layer
        if layer.providerType().lower() == "wfs":
            datasourceUri = QgsDataSourceURI(layer.source())
            url = datasourceUri.param("url") or layer.source().split("?")[0]
            url = url + "?service=WFS&version=1.1.0&REQUEST=GetCapabilities"
            try:
                if jsonp:
                    r, content = run(lambda: nam.request(url))
                    if "text/javascript" not in r.headers.values():
                        problems.append(
                            "Server for layer %s does not support JSONP. WFS layer won't be correctly loaded in Web App."
                            % layer.name())
                else:
                    r, content = run(
                        lambda: nam.request(url, headers={"origin": "null"}))
                    cors = r.headers.get("Access-Control-Allow-Origin",
                                         "").lower()
                    if cors not in ["null", "*"]:
                        problems.append(
                            "Server for layer %s is not allowed to accept cross-origin requests."
                            % layer.name())
            except:
                QgsMessageLog.logMessage(
                    "Warning: cannot verify if WFS layer server has the required configuration. Layer: '%s'."
                    % layer.name(),
                    level=QgsMessageLog.WARNING)

        if layer.type() != layer.VectorLayer:
            continue
        renderer = applayer.layer.rendererV2()
        allowed = [
            QgsSingleSymbolRendererV2, QgsCategorizedSymbolRendererV2,
            QgsGraduatedSymbolRendererV2, QgsHeatmapRenderer,
            QgsRuleBasedRendererV2
        ]
        try:
            allowed.append(QgsNullSymbolRenderer)
        except:
            pass
        if not isinstance(renderer, tuple(allowed)):
            problems.append(
                "Symbology used by layer %s includes unsupported elements."
                "Only single symbol, categorized, graduated, heatmap and rule-based renderers are supported."
                "This layer will not be correctly styled in the web app." %
                layer.name())
        if isinstance(renderer, QgsRuleBasedRendererV2):
            rules = renderer.rootRule().children()
            for rule in rules:
                expr = rule.filterExpression()
                unsupported = is_expression_supported(expr)
                if unsupported:
                    problems.append(
                        "The expression '%s' has unsupported functions: %s" %
                        (expr, ", ".join(unsupported)))

        if layer.hasLabelsEnabled():
            problems.append(
                "Layer %s uses old-style labeling. Labels might not be correctly rendered in the web app."
                % layer.name())
        if str(layer.customProperty("labeling/enabled")).lower() == "true":
            if unicode(layer.customProperty(
                    "labeling/isExpression")).lower() == "true":
                expr = layer.customProperty("labeling/fieldName")
                unsupported = is_expression_supported(expr)
                if unsupported:
                    problems.append(
                        "The expression '%s' has unsupported functions: %s" %
                        (expr, ", ".join(unsupported)))

    #TODO: check that layers using time attributes are not published using WMS

    hasTimeInfo = False
    for applayer in layers:
        if applayer.timeInfo is not None:
            hasTimeInfo = True
            break

    if hasTimeInfo and "timeline" not in appdef["Widgets"]:
        problems.append(
            "There are layers with time information, but timeline widget is not used."
        )

    if "timeline" in appdef["Widgets"]:
        for applayer in layers:
            layer = applayer.layer
            if layer.providerType().lower() == "wms":
                try:
                    source = layer.source()
                    url = re.search(r"url=(.*?)(?:&|$)", source).groups(0)[0]
                    layernames = re.search(r"layers=(.*?)(?:&|$)",
                                           source).groups(0)[0]
                    r, content = nam.request(
                        url + "?service=WMS&request=GetCapabilities")
                    root = ET.fromstring(
                        re.sub('\\sxmlns="[^"]+"', '', r.content))
                    for layerElement in root.iter('Layer'):
                        name = layerElement.find("Name").text
                        if name == layernames:
                            # look for discrete values
                            time = layerElement.find('Extent')
                            if time is not None:
                                applayer.timeInfo = time
                                hasTimeInfo = True
                            # look for interval values
                            time = layerElement.find('Dimension')
                            if time is not None and time.attrib[
                                    'name'] == 'time':
                                applayer.timeInfo = '"{}"'.format(time.text)
                                hasTimeInfo = True

                except:
                    #we swallow error, since this is not a vital info to add, so the app can still be created.
                    pass

        if not hasTimeInfo:
            problems.append(
                "Timeline widget is used but there are no layers with time information"
            )

    return problems