Technical migration 4.1 to 4.2


  1. Compatibility table plugins
  2. Parameter files (WEB-INF/params)
    1. runtime.xml
    2. Search models  
  3. Configuration files ($AMETYS_HOME/config)
    1. Connection via email
    2. Sorting data sources LDAP
  4. Workflow files
    1. Reference table workflow
    2. Synchronization workflow action
    3. Automatic workflow functions
  5. Tables SQL
    1. Tokens
    2. FO user registration
  6. Modification ofAPI
    1. Ribbon buttons
    2. Modification ofAPI for service parameters
    3. Setting PDF (custom fonts)
  7. Configuration parameters
    1. Url back office
  8. Data migration
    1. Label migration
    2. User registration service
    3. Change of storage format for multilingual data
    4. Former research department
    5. Service migration
  9. Applications Ametys ODF
  10. Other plugins
  11. Rights
  12. Solr and indexed data

Compatibility table plugins

Refer to the plugins compatibility table and increment the versions of your plugins (take the most recent 4.2-compatible version for each plugin ).

Parameter files (WEB-INF/params)

runtime.xml

In the runtime.xml, delete the following lines:

<org.ametys.cms.source.ContentView>org.ametys.web.source.WebContentView</org.ametys.cms.source.ContentView>                                    
<org.ametys.cms.source.ViewSelector>org.ametys.web.source.WebViewSelector</org.ametys.cms.source.ViewSelector>                                   

Search models  

In your search templates (the XML files under WEB-INF/param/search), replace occurrences of IndexingStringFieldAggregatorSearchUICriterion by IndexingFieldAggregatorSearchUICriterion

Configuration files ($AMETYS_HOME/config)

Connection via email

The FormsCredentialProvider configuration options have been modified:

  • the security level has been "split" into 2 options: allow cookie and captcha protection
  • a new parameter allows you to authorize the connection usingemail as the identifier


The file $AMETYS _HOME/config/user-population .xml must be modified manually.
Replace :

<runtime.authentication.form.security.level>low</runtime.authentication.form.security.level>                                 

by 

<runtime.authentication.form.cookies>true</runtime.authentication.form.cookies>           
<runtime.authentication.form.captcha>false</runtime.authentication.form.captcha>           
<runtime.authentication.form.login-by-email>false</runtime.authentication.form.login-by-email>                                                                       

Replace :

<runtime.authentication.form.security.level>high</runtime.authentication.form.security.level>                                 

by 

<runtime.authentication.form.cookies>false</runtime.authentication.form.cookies>           
<runtime.authentication.form.captcha>true</runtime.authentication.form.captcha>           
<runtime.authentication.form.login-by-email>false</runtime.authentication.form.login-by-email>                                                

Sorting data sources LDAP

The "results sorting" parameter has been brought up from the "Directory LDAP" type populations directly in the data source, in particular so that it can also be used by group directories.
In your files $AMETYS_HOME/config/user-populations .xml, delete all occurrences of the following parameter, if present:

<runtime.users.ldap.serverSideSorting>true</runtime.users.ldap.serverSideSorting>                                  

And in your files $AMETYS_HOME/config/datasources- ldap.xml, add the following parameter to each data source:

<serverSideSorting>true</serverSideSorting>                                     

The default value is "true". However, if you had unchecked "Sort results", i.e. you had set "runtime.users.ldap.serverSideSorting" to false in your directory population(s) LDAP for performance reasons, then set the value to "false" instead of "true" in the $AMETYS _HOME/config/datasources- ldap.xml to the corresponding data sources.

Workflow files

The WEB-INF/param/workflows.xml file is now optional. Available workflows are automatically read from the WEB-INF/param/workflows folder.

  1. Create a new WEB-INF/param/workflows folder.
  2. Open the file WEB-INF/param/workflows.xml.
  3. For each line, move the file indicated in the"location" attribute to the new folder
  4. Rename this file with the contents of the"name" attribute, to which you add the extension ".xml".
  5. Delete the file WEB-INF/param/workflows.xml.

In general, all you need to do is remove the "workflow-" prefix in front of the file name, but beware of a few pitfalls... for example, "worfklow.xml" becomes "content.xml"; or "workflow-coursepart.xml" becomes "course-part.xml". Pay particular attention to the "name" attribute in the workflows file.xml

Reference table workflow

Dans le fichier workflows/reference-table.xml pour les tables de référence, rajoutez l'action suivante dans les <common-actions> :

<action id="220" name="plugin.default-workflow:WORKFLOW_ACTION_ADD_CONTENTTYPE">                                                                                                                                                            
    <restrict-to>                                                                                                                                                                  
        <conditions type="AND">                                                                                                                                                                  
            <condition type="avalon">                                                                                                                                                                  
                <arg name="role">org.ametys.cms.workflow.LockCondition</arg>                                                                                                                                                                  
            </condition>                                                                                                                                                                  
        </conditions>                                                                                                                                                                  
    </restrict-to>                                                                                                                                                                  
    <results>                                                                                                                                                                  
        <unconditional-result old-status=" " status=" " step="1" />                                                                                                                                                                  
    </results>                                                                                                                                                                  
    <post-functions>                                                                                                                                                                  
        <function type="avalon">                                                                                                                                                                  
            <arg name="role">org.ametys.cms.workflow.SetCurrentStepIdAndNotifyFunction</arg>                                                                                                                                   
        </function>                                                                                                                                                                  
        <function type="avalon">                                                                                                                                                                  
            <arg name="role">org.ametys.cms.workflow.CreateVersionFunction</arg>                                                                                                                                   
        </function>                                                                                                                                                                  
    </post-functions>                                                                                                                                                                  
</action>                                                                                                                                                                  

and add the reference to this new action in reports (step) 1 and 3

<actions>                                                                                                                                                                 
     <common-action id="2" />                                                                                                                                                                 
     <common-action id="220" />                                                                                                                                                                 
     <common-action id="222" />                                                                                                                                                                 
</actions>                                                                                                                                          

Synchronization workflow action

On all workflows used in synchronization (i.e. using action 800), add to synchronization action 800 the post-function org.ametys.cms.workflow.ExtractOutgoingReferencesFunction :

<action id="800" name="plugin.contentio:WORKFLOW_ACTION_SYNCHRONIZE">                                        
 <restrict-to> [...] </restrict-to>                                        
 <results> [...] </results>                                        
 <post-functions>                                        
 <function type="avalon">                                        
 <arg name="role">org.ametys.cms.workflow.ExtractOutgoingReferencesFunction</arg>                                        
 </function>                                        
 </post-functions>                                        
</action>                                        

Automatic workflow functions

To simplify the installation of certain plugins functions, there is now an extension point for the 'after registration' and 'after validation' functions.

Places where you used to have the workflow functions of plugin forms or plugin translation-flagging will be replaced by generic functions.

For example, the function: org.ametys.plugins.forms.workflow.FormEditionFunction becomes

<function type="avalon">       
<arg name="role">org.ametys.cms.workflow.extensions.ExtensibleFunction</arg>       
<arg name="extension-point">org.ametys.cms.workflow.extensions.PostContentEditionFunctionsExtensionPoint</arg>       
</function>                    

and the org.ametys.plugins.translationflagging.TranslationAlertFunction becomes :

<function type="avalon">       
<arg name="role">org.ametys.cms.workflow.extensions.ExtensibleFunction</arg>       
<arg name="extension-point">org.ametys.cms.workflow.extensions.PostContentValidationFunctionsExtensionPoint</arg>       
</function>       

Tables SQL

Tokens

Tokens used to be for infinite or single use.
Now they can have a limited number of uses, be renewable and have a context of use.

Migration scripts SQL need to be run to update table formats (depending on your database type).

#Derby                                       
ALTER TABLE APP.Authentication_Token ADD nb_uses_left int;                                       
ALTER TABLE APP.Authentication_Token ADD auto_renew_duration bigint;                                       
ALTER TABLE APP.Authentication_Token ADD context VARCHAR(200);                                       
UPDATE APP.Authentication_Token SET nb_uses_left = 1 WHERE end_date IS NOT NULL;                                       
RENAME COLUMN APP.Authentication_Token.comment TO token_comment;                                       

#HsqlDb                                       
ALTER TABLE Authentication_Token ADD nb_uses_left INTEGER NULL BEFORE context;                                       
ALTER TABLE Authentication_Token ADD auto_renew_duration NUMERIC(13) NULL BEFORE context;                                       
ALTER TABLE Authentication_Token ADD context VARCHAR(200) NULL BEFORE context;                                       
UPDATE Authentication_Token SET nb_uses_left = 1 WHERE end_date IS NOT NULL;                                       
ALTER TABLE Authentication_Token ALTER COLUMN comment RENAME TO token_comment;                                       

#MySQL                                       
ALTER TABLE Authentication_Token                                       
ADD nb_uses_left INT NULL AFTER last_update_date,                                       
ADD auto_renew_duration BIGINT NULL AFTER nb_uses_left,                                       
ADD context VARCHAR(200) NULL AFTER auto_renew_duration;                                       
UPDATE Authentication_Token SET nb_uses_left = 1 WHERE end_date IS NOT NULL;                                       
ALTER TABLE Authentication_Token CHANGE comment token_comment LONGBLOB;                                       

#Oracle                                       
ALTER TABLE Authentication_Token ADD                                       
(                                       
 nb_uses_left NUMBER,                                       
 auto_renew_duration NUMBER(13),                                       
 context VARCHAR(200)                                       
);                                       
UPDATE Authentication_Token SET nb_uses_left = 1 WHERE end_date IS NOT NULL;                                       
ALTER TABLE Authentication_Token RENAME COLUMN comment TO token_comment;                                       

#PostgreSql                                       
ALTER TABLE Authentication_Token                                       
ADD nb_uses_left INTEGER NULL,                                       
ADD auto_renew_duration BIGINT NULL,                                       
ADD context VARCHAR(200) NULL;                                       
UPDATE Authentication_Token SET nb_uses_left = 1 WHERE end_date IS NOT NULL;                                       
ALTER TABLE Authentication_Token RENAME COLUMN comment TO token_comment;                                       

FO user registration

The account creation request no longer asks for first and last names (which will be requested later).

Run the following scripts on your database:

alter table Users_Temp drop lastname;                                                                                                    
alter table Users_Temp drop firstname;                                       

Modification ofAPI

Ribbon buttons

In your files java and plugin.xml, find and replace the occurrences in the 1st column of the table by their equivalent in the 2nd column:

 Replace

 By

org.ametys.cms.clientsideelement.SmartContentClientSideElement

 org.ametys.core.ui.StaticClientSideElement

org.ametys.cms.clientsideelement.SmartContentTypesGallery

 org.ametys.cms.clientsideelement.ContentTypesGallery

org.ametys.cms.clientsideelement.DeleteContentClientSideElement

 org.ametys.core.ui.StaticClientSideElement

org.ametys.cms.clientsideelement.ArchiveMenuClientSideElement

org.ametys.cms.clientsideelement.SmartContentMenu

org.ametys.web.clientsideelement.DeleteContentClientSideElement

org.ametys.core.ui.StaticClientSideElement

org.ametys.web.clientsideelement.TagClientSideElement

org.ametys.core.ui.StaticClientSideElement

org.ametys.web.clientsideelement.PageVisibilityClientSideElement

org.ametys.core.ui.StaticClientSideElement

org.ametys.web.clientsideelement.PreviewPageClientSideElement

org.ametys.core.ui.StaticClientSideElement

org.ametys.web.clientsideelement.LivePageClientSideElement

org.ametys.core.ui.StaticClientSideElement

org.ametys.web.clientsideelement.BlankPageClientSideElement

org.ametys.core.ui.StaticClientSideElement

org.ametys.web.clientsideelement.LinkPageClientSideElement

org.ametys.core.ui.SimpleMenu

Modification ofAPI for service parameters

The management of service parameters has been completely modified. When migrating a project to 4.2, if this project contains specific services or overloads kernel services (unless it only overloads rendering), this migration may require code modifications.

Setting PDF (custom fonts)

Dans certains projets (principalement ODF) les pipelines faisant appel à des PDF ont été modifiés pour intégrer des fonts personnalisées. Les pipelines personnalisés dans ce but ne sont donc plus utiles, il faut supprimer leurs appels et leurs déclarations. Vous pouvez les détecter en cherchant les serializers (sitemap.xmap) org.ametys.core.cocoon.FOPNGSerializer ayant une balise <user-config>. Les pipelines noyaux font désormais correctement l'appel à la configuration personnalisée.

Vérifier qu'il n'existe pas d'autres serializers spécifique au projet en cherchant <map:serialize type="fo2pdf"/>.

However, you may need to modify your project if the file is not positioned correctly. Please refer to the page Putting a custom font in a PDF for further information.

Configuration parameters

Url back office

In the general configuration parameters, CMS 'surl can no longer end with / or /index.html.
If this is the case, you need to change this parameter.

Data migration

With the server off, start by deleting the file $AMETYS_HOME/data/repository/repository/custom_nodetypes.xml then restart.

From the management console, run the following scripts:

Label migration

function copyTagNode(originalNode, newNode)                                                                                                    
{                                                                                                    
    setPropertyFromNode(originalNode, newNode, "ametys-internal:target");                                                                                                    
    setPropertyFromNode(originalNode, newNode, "ametys-internal:description");                                                                                                    
    setPropertyFromNode(originalNode, newNode, "ametys-internal:visibility");                                                                                                    
    setPropertyFromNode(originalNode, newNode, "ametys-internal:title");                                                                                                    
}                                                                                                    

function setPropertyFromNode(originalNode, newNode, propertyName)                                                                                                    
{                                                                                                    
    if (originalNode.hasProperty(propertyName))                                                                                                    
    {                                                                                                    
        newNode.setProperty(propertyName, originalNode.getProperty(propertyName).getString());                                                                                                    
    }                                                                                                    
}                                                                                                    

function createNodesFromNode(rootNode, sourceNode)                                                                                                    
{                                                                                                    
    var nbTag = 0;                                                                                                    
    var childNodes = sourceNode.getNodes();                                                                                                    

    while (childNodes.hasNext())                                                                                                    
    {                                                                                                    
        var childNode = childNodes.next();                                                                                                    
        var createdNode = rootNode.addNode(childNode.getName(), "ametys:cmstag");                                                                                                    
        copyTagNode(childNode, createdNode);                                                                                                    
        nbTag++;                                                                                                    
        nbTag += createNodesFromNode(createdNode, childNode);                                                                                                    
    }                                                                                                    
    return nbTag;                                                                                                    
}                                                                                                    

var totalNbTag = 0;                                                                                                    

var qm = session.getWorkspace().getQueryManager();                                                                                                    
var query = qm.createQuery("//element(*, ametys:tags)", javax.jcr.query.Query.XPATH);                                                                                                    
var nodes = query.execute().getNodes();                                                                                                    
while (nodes.hasNext())                                                                                                    
{                                                                                                    
    var tagsNode = nodes.next();                                                                                                    
    if (!tagsNode.getParent().hasNode("cmstags"))                                                                                                    
    {                                                                                                    
        tagsNode.getParent().addNode("cmstags", "ametys:cmstags");                                                                                                    
    }                                                                                                    
                                                                                                    
    var newTagsNode = tagsNode.getParent().getNode("cmstags");                                                                                                    
    totalNbTag += createNodesFromNode(newTagsNode, tagsNode);                                                                                                    
    tagsNode.remove();                                                                                                    
}                                                                                                    

session.save();                                                                                                    
print(totalNbTag + " tag(s) have been migrated");                             

User registration service

The service parameters have been modified:

  • The choice of UGC display mode becomes a drop-down list: never / link to a page / insert content
  • The choice of behavior at the end of account creation: standard message / standard message and link to a page / content insertion

Run the following script JCR to modify existing services:

var count = 0;                                                                                             
var qm = session.getWorkspace().getQueryManager();                                                                                                 
var query = qm.createQuery("//element(*, ametys:zoneItem)[@ametys-internal:service = 'org.ametys.web.service.UserSignup']", javax.jcr.query.Query.XPATH);                                                                                                 
var nodes = query.execute().getNodes();                                                                                                 
                             
while (nodes.hasNext())                                                                                                
{                                                                                             
    var node = nodes.next();                                                                                             
    var page = node.getParent().getParent().getParent().getParent();                                                                                             
    var sitemap = page.getProperty("ametys:sitemap").getString();                                                                                             
                                                                                                 
    print("Found a signup service for language " + sitemap + " at path " + page.getPath());                                                                                             
    count++;                                                                                             
                                                                                                 
    var params = node.getNode("ametys:service_parameters");                                                                                             
    if (params.hasProperty("ametys:terms-of-service-page"))                                                                                             
    {                                                                                             
          var pageId = params.getProperty("ametys:terms-of-service-page").getString();                                                                                             
       if (pageId != '')                                                                                             
       {                                                                                             
          params.setProperty("ametys:terms-of-service-mode", "PAGE");                                                                                             
       }                                                                                             
       else                                                                                             
       {                                                                                             
         params.setProperty("ametys:terms-of-service-mode", "NONE");                                                                                             
       }                                                                                             
    }                                                                                             
    else                                                                                             
    {                                                                                             
      params.setProperty("ametys:terms-of-service-mode", "NONE");                                                                                             
    }                                                                                             
                                                                                                 
    query = qm.createQuery("//element(*, ametys:page)[@ametys-internal:tags = 'USER_PREFS_MAIN' and @ametys:sitemap= " + sitemap + "]", javax.jcr.query.Query.XPATH);                                                                                                 
    var pages = query.execute().getNodes();                                                                                                 
    if (pages.hasNext())                                                                                                
    {                                                                                                
        var successPage = pages.next();                                                                                             
        params.setProperty("ametys:success-page", "page://" + successPage.getUUID());                                                                                             
        params.setProperty("ametys:success-mode", "PAGE");                                                                                             
    }                                                                                        
                             
    // TO UNCOMMENT ONLY IF YOU USE USER-DIRECTORY PLUGIN                                                                                   
    // params.setProperty("ametys:xslt", pages/services/user-signup/signup.xsl);                                                                                    
}                                                                                             
                             
session.save();                                                                                             
print(count + " signup services have been migrated");                                        

Change of storage format for multilingual data

var fieldsByContent = new java.util.HashMap();                                                                                                                  
fillFieldsToMigrateMap();                                                                                                                  

var migratedFields = 0;                                                                                                                  
var migratedContents = 0;                                                                                                                  

// Migrate the multilingual nodes content by content                                                                                                                  
fieldsByContent.forEach(                                                                                                                  
    function(contentId)                                                                                                                  
    {                                                                                                                  
        var content = ametysResolver.resolveById("content://" + contentId);                                                                                                                   
        migrateContent(content,                                                                                                                  
            [renameMultilingualString],                                                                                                                  
            true /* old versions have to be marked incompatible */,                                                                                                                  
            null /* tag new versions */,                                                                                                                  
            false /* not verbose - DO NOT ACTIVATE IN THIS CASE !! */                                                                                                                  
        );                                                                                                                  
    }                                                                                                                  
);                                                                                                                  

print(migratedFields + " multilingual fields have been migrated in " + migratedContents + " contents.");                                                                                                                    

// Get all the multilingual nodes                                                                                                                    
function fillFieldsToMigrateMap()                                                                                                                  
{                                                                                                                    
    var fieldsToMigrate = 0;                                                                                                                    

    var qm = session.getWorkspace().getQueryManager();                                                                                                                    

    var query = qm.createQuery("//element(*, ametys:multilingualString)", javax.jcr.query.Query.XPATH);                                                                                                                     
    var nodes = query.execute().getNodes();                                                                                                                    

    while (nodes.hasNext())                                                                                                                  
    {                                                                                                                  
        var node = nodes.next();                                                                                                                  

        // Get the path of the multilingual metadata to migrate                                                                                                                  
        var metadataPath = "";                                                                                                                  
        var contentNode = node;                                                                                                                  
        while (!contentNode.getPrimaryNodeType().isNodeType("ametys:content"))                                                                                                                  
        {                                                                                                                  
            metadataPath += node.getName() + "/";                                                                                                                  
            contentNode = node.getParent();                                                                                                                  
        }                                                                                                                  
        metadataPath = metadataPath.slice(0, -1);                                                                                                                    

        // Store the metadataPath to migrate by content ID                                                                                                                     
        var contentId = contentNode.getIdentifier();                                                                                                                    
        var fields = fieldsByContent.getOrDefault(contentId, new java.util.HashSet());                                                                                                                    
        fields.add(metadataPath);                                                                                                                    
        fieldsByContent.put(contentId, fields);                                                                                                                    
        fieldsToMigrate++;                                                                                                                    
    }                                                                                                                    

    print(fieldsToMigrate + " multilingual fields to migrate potentially in " + fieldsByContent.keySet().size() + " contents.");                                                                                                                  
}                                                                                                                  

// Migrate the multilingual strings of the content and increment the global counter                                                                                                                  
function renameMultilingualString(content)                                                                                                                  
{                                                                                                                  
    var migratedContent = false;                                                                                                                  
    var contentId = content.getNode().getIdentifier();                                                                                                                  

    // Get all metadata to migrate for the current content                                                                                                                  
    var fields = fieldsByContent.get(contentId);                                                                                                                  
    fields.forEach(                                                                                                                  
        function(fieldName)                                                                                                                  
        {                                                                                                                  
            var migratedField = false;                                                                                                                  
            var fieldNode = content.getNode().getNode(fieldName);                                                                                                                  
            var properties = fieldNode.getProperties();                                                                                                                  
            while (properties.hasNext())                                                                                                                  
            {                                                                                                                  
                var property = properties.nextProperty();                                                                                                                  

                // If the current property name doesn't starts with "ametys:" ...                                                                                                                  
                var propertyName = property.getName();                                                                                                                  
                if (propertyName.indexOf(":") == -1)                                                                                                                  
                {                                                                                                                  
                    // ... move it to "ametys:[propertyName]"                                                                                                                  
                    fieldNode.setProperty("ametys:" + propertyName, property.getValue());                                                                                                                  
                    property.remove();                                                                                                                  
                    migratedField = true;                                                                                                                  
                }                                                                                                                  
            }                                                                                                                  
            if (migratedField)                                                                                                                  
            {                                                                                                                  
                migratedFields++;                                                                                                                  
                migratedContent = true;                                                                                                                  
            }                                                                                                                  
        }                                                                                                                  
    );                                                                                                                  

    if (migratedContent)                                                                                                                  
    {                                                                                                                  
        migratedContents++;                                                                                                                  
    }                                                                                                                  
}                                             

Former research department

The main search engine service has been renamed "Page Search".

The associatedurl service has been changed from "service/search.html" to "service/search-pages.html". Check your XSL to make sure you didn't have this url .

The service class has changed. If the search engine has been duplicated, search your project's xml files and replace "org.ametys.web.frontoffice.SearchService" with "org.ametys.web.frontoffice.SearchPagesService".

The location of the service's XSL has changed(see graphic migration), so you need to run the following script (and rebuild live if there are any changes):

var count = 0;                                    
var done = 0;                                    
                                        
jcrXPathQuery("//element(*, ametys:zoneItem)[@ametys-internal:service='org.ametys.web.service.FrontSearchService']").forEach(function(zi) {                                    
     print("Found " + zi.getPath());                                    
     var parameters = zi.getServiceParameters();                                    
     var xsltParam = parameters.getValue("xslt");                                    
     print(" " + xsltParam);                                    
     count++;                                    
                                        
     var toReplace = "pages/services/search/";                                    
     if (xsltParam.startsWith(toReplace))                                    
     {                                    
        var newValue = "pages/services/search-pages/" + xsltParam.substring(toReplace.length);                                    
        print(" => " + newValue)                                    
        parameters.setValue("xslt", newValue);                                    
        zi.saveChanges();                                    
        done++;                                    
     }                                    
     else                                    
     {                                    
        print(" => ok")                                    
     }                                    
})                                    
                                        
print("...");                                    
print(done + " services modifiés sur " + count)                                    
if (done > 0)                          
{                                    
  print("PENSEZ A RECONSTRUIRE LE LIVE")                                    
}                           

Service migration

Following changes toAPI and data storage, existing services need to be migrated.
Go to script on the page dedicated to service migration.

Applications Ametys ODF

If your application is a Ametys ODF application, follow the technical and graphical migration guide from version 4.0 to version 4.1 of theODF

Other plugins

Depending on the plugins Ametys you use, you must follow the technical and/or graphic migrations specific to each plugin :

Rights

The "Modify search Solr" right has been removed in favor of the already existing "Search tool Solr" right.

Solr and indexed data

The Solr server must be reinstalled.
Download version 4.2 http://releases.ametys.org/release/org.ametys / solr-app/4 .2.x/4.2.0/zips/

Do not keep the old indexed data (delete all the contents of "solr home" (except the logs folder, if any), by default it is located in solr/server/solr, then copy and paste the default data from the delivery of ametys-solr-config-*.zip).

After restarting the server Solr then Ametys, run a full indexing operation.

Back to top