Pre-Upgrade Scripting (6.2 > DXP) - Pt 3 - Web Content Version Cleanup

General Blogs May 7, 2018 By Daniel Tyger

We have a lot of distributed web content management in our Liferay installation and many articles are undoubtedly edited many times over a period of years. Let's take a look...:

select count(*) from journalarticle; Returns 19,984 records

And, via the script console we can see there are much fewer unique articles:

out.println(com.liferay.portlet.journal.service.JournalArticleResourceLocalServiceUtil.getJournalArticleResourcesCount())

which returns 3,861 articles. I think we can make some headway, here, too. An "article" resource object actually contains all of its corresponding historical versions and the link between them is the ResourcePrimKey, which is also what is needed to fetch the latest version of a given article. You can get an idea of how many unique versions might exist on a given article, use: select distinct version from journalarticle;  We were shocked to see an article edited as many as 221 times!

Here is the script so far. It can process any number of web content resources or *all* of them. Feel free to comment on potential improvements...:

 

Pre-Upgrade Scripting (6.2 > DXP) - Pt 2 - Document Version Cleanup

General Blogs April 30, 2018 By Daniel Tyger

Do you have a lot of documents in your Liferay instance? It may be worth it to clean out old document versions... Let's take a look:

mysql> select count(*) from dlfileentry; Returns 12,300.
mysql> select count(*) from dlfileversion; Returns 13, 678.

Looks worth it to remove historic versions. I decided to explore one of our sites that has a lot of documents in their repository, using Mitesh Panchal's excellent script for targeting document version deletions by site and folderId. I added a preview mode option and some logging and exception catches and figured out that if you use "0" for the folderId, the script will crawl the entire site's set of folders. Awesome start.

Upon reviewing the results of this first site's log of traced transactions, I discovered some flawed data..:

Folder Name: Supplemental Course Links...
Deleting >>1.4 with Title=Testing Success and Remediation Plan.pdf
Deleting >>1.2 with Title=Testing Success and Remediation Plan.pdf
Deleting >>1.1 with Title=Testing Success and Remediation Plan.pdf
Folder Name: Professio...

It looks like version 1.4 would be deleted and 1.3 is being retained. Indeed, Liferay's database table dlfileentry version=  record is not the true *latest* version for the same file as listed in the dlfileversion table. Time to see how pervasive this problem is. After some inquiries, I learned there were more than 50 cases across a variety of sites where this problem scenario recurred. In this case we will use DLFileVersionLocalServiceUtil.getLatestFileVersion() to get the accurate latest version for each file and delete the rest.

For this use case, this variation on Mitesh's script works well for any given site and or specific folder. But we want to clean them all out. Here is a version that is reworked to accomplish that with a dynamic query to improve performance...:

But this script did not run, due to the errors being caught for every dlfileentry record that had *no* corresponding dlfileversion record. It turns out that after installing XTivia's "Documents and Media File System Checker" portlet that these same records were also showing up in that portlet's output as having no file existing in the file system. The entries are useless to end users and do not even list out in the UI. Makes sense, but does make this harder...

Enter a script to delete a targeted list of fileEntryIds that are the offending / missing documents. Here is a mysql statement that identifies file entries with no version records...:

select fileEntryId from dlfileentry where fileEntryId NOT IN (select dlfe.fileEntryId from dlfileentry dlfe, dlfileversion dlfv where dlfe.fileEntryId=dlfv.fileEntryId and dlfv.version=dlfe.version);

Here is a quick script to target-delete a list of specific fileEntryIds you want to remove from the system. Once this script is run, the full document version cleanup script will run.

 

Pre-Upgrade Scripting (6.2 > DXP) - Pt 1 - Delete Layouts

General Blogs April 24, 2018 By Daniel Tyger

Getting ready to upgrade to DXP from Liferay 6.2? You reached the section of the documentation about pre-upgrade preparations that will speed up the process? Me, too. With much appreciation to Sébastien Le Marchand for his excellent article "5 tips to improve usage of the Liferay script console" and Corné Aussems for his public groovy scripts that propelled me along.

I would like to offer some additional scripts that do achieve some of the tasks recommended in Liferay's pre-upgrade tips as they affect our particular environment. We will use the Server Administration > Script Console for our work. And, we will always use Sebastian's "preview mode" feature as we test run our scripts.

This blog entry will be about bulk-removing pages (LAYOUT) and targeting a collection of pages using ActionableDynamicQuery API. We have surpassed 80,000 users and each user is issued a personal site of 3 pages with several portlets deployed on each. It turns out our audiences are not using the platform for this feature and we will look to remove most, if not all, the pages of each of the personal sites. This will, in turn, help to reduce our resourcepermission table's 2 million+ entries. We are fairly certain we will at least delete 2/3 pages - each named "Blog" (/blog) and "Friends," (/friends) respectively.

Let's see what we have for some counts before we begin:

mysql> select count(*) from layout where friendlyURL = '/blog'; (Returns: 81350)
mysql> select count(*) from layout where friendlyURL = '/friends'; (Returns 81354)
mysql> select count(*) from resourcepermission; (Returns 2,115,503)

Wow - that's a lot of layouts! We'll want to make sure the script runs through the entire database before crapping out.

We can thank Michael Bowerman for contributing significant improvements to the efficiency and flexibility of an earlier version. Let's take a look over the final script and Michael's great advices regarding Liferay's "DynamicQuery" along with the "Disjunction" functionality to delete all pages of either friendlyURL...:

By using ActionableDynamicQuery, you can fetch and process objects in smaller batches, substantially improving performance. To use the ActionableDynamicQuery method, you would first create a new ActionableDynamicQuery object corresponding to the type of model you want to process. In this case, the type of model you are processing is Layout, so you would create a LayoutActionableDynamicQuery object (in the com.liferay.portal.service.persistence package). When you create the ActionableDynamicQuery object, you can override the default methods from the BaseActionableDynamicQuery class. The two methods you'll probably be interested in overriding are:

1. public void addCriteria(DynamicQuery dynamicQuery)
- What the addCriteria method does is it allows you to filter down criteria for objects when fetching them. If you do not implement this method, then all objects of that model type in the database will be fetched. If you do implement the method, you can use it to specify criteria by which objects will be fetched (for instance, in this case, we would want to use this method to only fetch Layouts whose friendlyURL is "/friends"). You will be using hibernate's criteria API when you implement this method
AND
2. protected void performAction(Object object)
- The performAction method specifies the action to be performed on each object that is fetched. In this case, the object we have fetched is a layout, and the action we will want to perform on it is to delete that layout.

Once you have created the ActionableDynamicQuery object that implements these two methods (or at least implements the performAction method), then you can simply invoke actionableDynamicQuery.performActions to fetch the entries and invoke the action on them.

The advantage of ActionableDynamicQuery is that it only fetches in batches, using a pre-set size (by default, 10000). Once it finishes processing the first batch, it then fetches the second batch to be processed, and so on and so forth, until it has processed every batch. In comparison to fetching every object into a single list, the performance improvement can actually be quite large depending on how many objects you are fetching.

For further exploration of DynamicQuery API in Liferay:

The script took some time to run so, we split the work into two scripts (1 for "/blog' pages and one for '/friends' pages). An exception occurred and when I dug in I noticed the failed layout's page owner had changed the page type of one of the pages to URL page type and thus became undeleteable. Other than that, these scripts wiped out all the desired layouts and reduced the resourcepermission table by more than 50%. Win.

ResourcePermission Table Before: 2,115,503. After: 898,188!

Cheers. I'll share my next decent script after I complete it...

API Integration with HaveIBeenPwned w Structure and Template

Technical Blogs June 15, 2016 By Daniel Tyger

We recently had a request for our users to be able to check if their email address has been a victim of a known data breach per the awesome API and data stores provided at https://haveibeenpwned.com/. Here is the API: https://haveibeenpwned.com/API/v2 and some background story of this project and why it's worthwhile. There are some already-written consumer examples shared there, but I chose to try and fashion a lightweight version of my own that could be quickly deployed to a Liferay page without developing an entire portlet for this goal.

I was able to achieve this utilizing Liferay Templates and Structures, JavaScript, Freemarker (or Velocity), HTML and CSS. I'll provide a few basic steps and enough (Freemarker) code to get you going. This mini-app greets the user and offers him/her to check their email address for known breaches. The code makes an AJAX call to the HaveIBeenPwned API and returns JSON data (if data is found) that is parsed and formatted for display in HTML / CSS. 

1) Create a simple structure. I named mine "PWNED". Add a text field (that we won't use) to it and save / publish it.

2) Create a (Freemarker) template. I named mine "PWNED". Attach this template to the "PWNED" structure from step 1. Uncheck the "Cacheable" option. Add the code below to the template, then Save / Publish it:

<#assign user = permissionChecker.getUser() />
<!DOCTYPE html>
<html>
<body>
<style>
.attention {
color: red;
}
.breachIntro {
font-size: 16px;
}
</style>
<h2>Hello, ${user.firstName}!</h2>
<p class="breachIntro">Check if your email address been victim of a known data breach:</p>
<form id="checkBreaches" name="checkBreaches">
<label for="Email">Email address:</label>
<input id="checkEmail" type="text" name="Email" value="" placeholder="${user.emailAddress}">
<input type="submit" name="submit" value="Check It!" onclick="loadDoc();return false;">
<p><a href="#" onclick='reloadMe();'>Refresh Page / Search Again</a></p>
</form>
<div id="breachInfo"></div>
<script>
function loadDoc() {
  var xhttp = new XMLHttpRequest();
  var url =  "https://haveibeenpwned.com/api/v2/breachedaccount/";
  if (document.getElementById("checkEmail").value == "") {
  var email = "${user.emailAddress}";
  }
  else {
    var email = document.getElementById("checkEmail").value;
  }
  var checkUserURL = url + email;
  xhttp.onreadystatechange = function() {
    if (xhttp.readyState == 4 && xhttp.status == 200) {
      writeData(xhttp.responseText);
    }
    else if (xhttp.readyState == 4 && xhttp.status == 404){
    document.getElementById("breachInfo").innerHTML = "<h4><p>404 - No data returned for  <span class=\"attention\">\"" + email + ".\"</span> This is a good thing! It appears you have no known associated breaches with this account.</p></h4>";
    }
  };
  xhttp.open("GET", checkUserURL, true);
  xhttp.send();
}
function writeData(response) {
var arr = JSON.parse(response);
var i;
var email = document.getElementById("checkEmail").value;
var out = "<h4><span class=\"attention\">Unfortunately, it looks like \"" + email + "\" has been breached. </span>Please see the details below:</h4>" + "<div class=\"breachEntries\">";
for(i = 0; i < arr.length; i++) {
        out += "<div class=\"breachEntry\" id=\"breachEntry" + i + "\"><div class=\"breachDomain\"><strong>Domain of Breach:&nbsp;</strong>" + arr[i].Name +
        "&nbsp;(<a href=\"http://" + arr[i].Domain + "\" target=\"_blank\"><strong>http://" + arr[i].Domain + "</a></strong>):&nbsp;" +
        "</div><div class=\"breachDesc\">" +
        arr[i].Description +
        "</div><div class=\"breachItems\"><strong>Breached Items:&nbsp;</strong>" +
        arr[i].DataClasses +
        "</div><div class=\"breachDate\"><strong>Breach Date:&nbsp;</strong>" +
        arr[i].BreachDate +
        "</div></div><hr>";
        }
    out += "</div>";
document.getElementById("breachInfo").innerHTML = out;
}
function reloadMe() {
window.location = window.location.pathname;
}
</script>
<div>(Courtesy of "<a href="https://haveibeenpwned.com/" target="_blank">https://haveibeenpwned.com/</a>" and Daniel Tyger for the Liferay steps / code.)
</div>
</body>
</html>
3) Create a web content object, using the newly-created structure / template combination from steps 1-2. Publish / save it and deploy it to a Liferay page. You should see something like the following:
If you enter "foo@bar.com" into the "Email address" field, you will see what the data results look like when parsed...:

and - a look at the resulting HTML:

You could easily paint a table, instead, and include other fields from the API I have omitted in this example...

Explanation of the code:

1) Create the Freemarker "user" variable we will utilize to display the user's first name and email address where we need it later in the app...:

<#assign user = permissionChecker.getUser() />
....
<h2>Hello, ${user.firstName}!</h2>
...
var email = "${user.emailAddress}";

2) Create the form and the <div> for displaying the results:

<form id="checkBreaches" name="checkBreaches">
<label for="Email">Email address:</label>
<input id="checkEmail" type="text" name="Email" value="" placeholder="${user.emailAddress}">
<input type="submit" name="submit" value="Check It!" onclick="loadDoc();return false;">
<p><a href="/group/mycampus/identity-theft">Refresh Page / Search Again</a></p>
</form>
<div id="breachInfo"></div>

3) Set up the forked AJAX function call  to the API to use either the Liferay user emailAddress or the one entered by the user into the form and to write a success message if no data is returned:

function loadDoc() ...

and upon successful data results, fire the writeData function:

    if (xhttp.readyState == 4 && xhttp.status == 200) {
      writeData(xhttp.responseText);
    }

4) Parse the results for display in the writeData() function:

function writeData(response) {
var arr = JSON.parse(response);
var i;
var email = document.getElementById("checkEmail").value;
var out = "<h4><span class=\"attention\">Unfortunately, it looks like \"" + email + "\" has been breached. </span>Please see the details below:</h4>" + "<div class=\"breachEntries\">";
for(i = 0; i < arr.length; i++) {
        out += "<div class=\"breachEntry\" id=\"breachEntry" + i + "\"><div class=\"breachDomain\"><strong>Domain of Breach:&nbsp;</strong>" + arr[i].Name +
        "&nbsp;(<a href=\"http://" + arr[i].Domain + "\" target=\"_blank\"><strong>http://" + arr[i].Domain + "</a></strong>):&nbsp;" +
        "</div><div class=\"breachDesc\">" +
        arr[i].Description +
        "</div><div class=\"breachItems\"><strong>Breached Items:&nbsp;</strong>" +
        arr[i].DataClasses +
        "</div><div class=\"breachDate\"><strong>Breach Date:&nbsp;</strong>" +
        arr[i].BreachDate +
        "</div></div><hr>";
        }
    out += "</div>";
document.getElementById("breachInfo").innerHTML = out;
}

Hope it works for you. Please respond if it does not. Suggestions welcome. It's brand new for me...

Expand Your Brand - Add Fav and Touch Icon Options to Liferay

General Blogs December 2, 2015 By Daniel Tyger

Isn't this Favicon Thing a Simple Problem to Solve?

Not anymore. That's especially true, if you are picky.

Do you really want a generic letter as your users' bookmarked icon on their mobile devices? Are your logs filling up with WARN messages that various devices can't find the optimal icon they are seeking? Well, each one may represent a lost opportunity to expand your brand impact and user confidence in your site...:

14:18:19,487 WARN [http-bio-8443-exec-379][code_jsp:130]{code="404", msg="/apple-touch-icon-152x152.png", uri=/apple-touch-icon-152x152.png}
14:18:19,597 WARN [http-bio-8443-exec-374][code_jsp:130]{code="404", msg="/apple-touch-icon.png", uri=/apple-touch-icon.png}
05:37:07,635 WARN [http-bio-8443-exec-341][code_jsp:130]{code="404", msg="/apple-touch-icon-76x76-precomposed.png", uri=/apple-touch-icon-76x76-precomposed.png} 

These are the signs that tipped us off we weren't comprehensively solving this issue as well as we could.

The good news is this is easy to expand, control, or enhance from the out-of-the-box setup of most Liferay themes as of this writing.

So, now we are tasked with improving the mobile and web site branding element historically known as "favicon.ico" and on some mobile devices as "touch" icons and others as "tiles." With all the craziness of expanding OS / browser / version combinations, it's becoming hard to decide where to draw the line on how much effort to put into refining this presentation on various target user platforms.

Here are a couple great resources we utilized in our efforts and is suggested background preparation before moving on in the article:

What is the Default Liferay Setup?

Each theme in Liferay has a favicon.ico available in the /images folder. You may recognize it:

So - to take immediate control of that branding icon as-is, you can copy this file to your /diffs/images/ folder and overwrite it with your own favicon.ico file. Now, you may have something custom like this showing up in (some of) your browser tabs:

However, doing only this, you will still not be meeting the needs of a lot of devices with regard to bookmarking and adding the icon to screens, especially iOS and other mobile device platforms. You will end up with generic bookmark icons being generated when users bookmark your site or even add a favorite page of it to their home screen of their mobile, tablet devices:

or or

These generic icons are being generated because you aren't yet providing the additional images the browsers are looking for to display an intentional icon on their respective devices. The devices will search by default in the site's root folder for the images, which don't exist.

Take Full Control

This was our method / solution, which we may tweak or change significantly in the future, and it's not for everyone, but hopefully gives you ideas to what degree you may choose to adopt it. This solution would meet the needs of common tiled bookmark platforms, all iOS platforms, all common Android Chrome platforms, and all common larger desktop / laptop platform combos. It is also future-proofed for iOS7+. This was based on extrapolations from the article from Mathias Bynens: "Everything you always wanted to know about touch icons" and the great work being done at: RealFavIconGenerator.net.

Instance-wide solution:

  1. Using the RealFavIconGenerator submit a nice graphic of your desired icon at the recommended  resolution (260 x 260). After it is processed, paste the provided code into a text editor and download and expand the .ZIP package of icons / objects you can deploy to your site.
  2. We next embed the additional link codes to the HEAD of each page across Liferay instance. Create a simple JSP hook to override /html/common/themes/top_head.jsp. We chose to replace:

    <link href="<%= themeDisplay.getPathThemeImages() %>/<%= PropsValues.THEME_SHORTCUT_ICON %>" rel="Shortcut Icon" />

    with something like this:

    <link rel="icon" type="image/png" href="/favicon-194x194.png" sizes="194x194">
    <link rel="icon" type="image/png" href="/android-chrome-192x192.png" sizes="192x192">
    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png">
    <link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png">
    <link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png">
    <link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png">
    <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png">
    <link rel="manifest" href="/manifest.json">
    <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#1c667b">
  3. Create a simple JSP hook to utilize /html/common/themes/top_meta-ext.jsp. Note - you could easily combine both .jsp files in one hook project. We added something like this:

    <meta name="msapplication-TileColor" content="#da532c">
    <meta name="msapplication-TileImage" content="/mstile-144x144.png">
    <meta name="msapplication-config" content="/browserconfig.xml">
    <meta name="theme-color" content="#ffffff">
  4. SFTP the files from step 1 that you included in steps 2-3 to the /ROOT folder of your web server (/webapps/ROOT/ for tomcat servers...).
    Here is our list of files to SFTP:
    -favicon.ico
    -apple-touch-icon-precomposed.png
    -apple-touch-icon.png
    -favicon-194x194.png
    -android-chrome-192x192.png
    -apple-touch-icon-152x152.png
    -apple-touch-icon-120x120.png
    -apple-touch-icon-76x76.png
    -apple-touch-icon-57x57.png
    -manifest.json
    -safari-pinned-tab.svg
    -mstile-144x144.png
    -browserconfig.xml

Per-Theme Solution (allow each theme to define its own bookmark icons):

  1. Using the RealFavIconGenerator submit a nice graphic of your desired icon at the recommended  resolution (260 x 260). After it is processed, paste the provided code into a text editor and download and expand the .ZIP package of icons / objects you can deploy to your site.
  2. We next embed the additional link codes to the HEAD of each page across Liferay instance. Create a simple JSP hook to override /html/common/themes/top_head.jsp. We chose to replace:

    <link href="<%= themeDisplay.getPathThemeImages() %>/<%= PropsValues.THEME_SHORTCUT_ICON %>" rel="Shortcut Icon" /> 

    with something like this:

    
    <link rel="shortcut icon" href="<%= themeDisplay.getPathThemeImages() %>/icons/favicon.ico">
    <link rel="icon" type="image/png" href="<%= themeDisplay.getPathThemeImages() %>/icons/favicon-194x194.png" sizes="194x194">
    <link rel="icon" type="image/png" href="<%= themeDisplay.getPathThemeImages() %>/icons/android-chrome-192x192.png" sizes="192x192">
    <link rel="apple-touch-icon" sizes="180x180" href="<%= themeDisplay.getPathThemeImages() %>/icons/apple-touch-icon-180x180.png">
    <link rel="apple-touch-icon" sizes="152x152" href="<%= themeDisplay.getPathThemeImages() %>/icons/apple-touch-icon-152x152.png">
    <link rel="apple-touch-icon" sizes="120x120" href="<%= themeDisplay.getPathThemeImages() %>/icons/apple-touch-icon-120x120.png">
    <link rel="apple-touch-icon" sizes="76x76" href="<%= themeDisplay.getPathThemeImages() %>/icons/apple-touch-icon-76x76.png">
    <link rel="apple-touch-icon" sizes="57x57" href="<%= themeDisplay.getPathThemeImages() %>/icons/apple-touch-icon-57x57.png">
    <link rel="manifest" href="<%= themeDisplay.getPathThemeImages() %>/icons/manifest.json">
    <link rel="mask-icon" href="<%= themeDisplay.getPathThemeImages() %>/icons/safari-pinned-tab.svg" color="#1c667b">
  3. Create a simple JSP hook to utilize /html/common/themes/top_meta-ext.jsp. We added something like this:

    <meta name="msapplication-TileColor" content="#da532c">
    <meta name="msapplication-TileImage" content="<%= themeDisplay.getPathThemeImages() %>/icons/mstile-144x144.png">
    <meta name="msapplication-config" content="<%= themeDisplay.getPathThemeImages() %>/icons/browserconfig.xml">
    <meta name="theme-color" content="#ffffff">
  4. Create a folder in all your theme /_diffs folder: /_diffs/images/icons
  5. SFTP the files from step 1 that you included in steps 2-3 to the folder created in step 4.
  6. Replace any default favicon.ico file Liferay may have included with your desired favicon.ico file - e.g in /webapps/ROOT/ you may still want to include an overall

There are Easier Ways

Use rewrite rules... fewer images... for example, see David Hund's "One Icon to Rule Them All" approach / reaction to this same issue:

RewriteRule ^apple-touch-icon(.*).png$ apple-touch-icon.png [L]

Also, see AudreyR's Favicon cheat sheet for another take on obsessive versus compulsive.

Liferay 6.2EE Resource Permissions Reports - Pt 1

General Blogs January 9, 2015 By Daniel Tyger

Background and Need:

Our campus portal (Site) administrators have a need to review permissions across their sites of various objects. In particular, we see requests for page, portlet, and Web Content permissions reports that we currently offer our site managers. They have also expressed desire for Document Library permission reports. We have over 1600 roles in Liferay and some sites have several hundred pages, so you can imagine this managerial task being daunting on a resource-by-resource review of items. I have begun exploring a solution in Liferay 6.2EE.

Liferay “Permission 6” Algorithm:

First, we must understand Liferay 6.2 permission algorithm. Take a “Deep Dive” into Liferay 6+ roles and permissions. Here is another similar explanation. Also, we need to understand bitwise values and extracting them from summed integers in the resourcepermissions table against their resourceaction models for various object privileges that may be assigned.

Liferay EE Reporting Solution Plugins:

Liferay EE customers have access to the following two MarketPlace plugin applications:

These two portlets combined can be deployed to the portal instance and provide adequate replacement of reporting technology. You’ll have to do some setup. Go through the Liferay Reporting User Guide documentation. Note that you will need to create appropriate queries and corresponding report definition files in the form of .JRXML to meet Jaspersoft’s requirements to produce the reports.

 

Page Permissions Walkthrough:

To retrieve the possible bitwise permission values of the page layout model, run this:

select * from resourceaction where name = 'com.liferay.portal.model.Layout';

+------------------+---------------------------------+----------------------------------------+---------------+

| resourceActionId | name                            | actionId                   | bitwiseValue |

+------------------+---------------------------------+-----------------------------------------+--------------+

|              360 | com.liferay.portal.model.Layout | ADD_DISCUSSION     |            2     |

|              980 | com.liferay.portal.model.Layout | ADD_LAYOUT             |            8     |

|              981 | com.liferay.portal.model.Layout | CONFIGURE_PORTLETS |           16     |

|              982 | com.liferay.portal.model.Layout | CUSTOMIZE              |           32     |

|              983 | com.liferay.portal.model.Layout | DELETE                 |           64     |

|              984 | com.liferay.portal.model.Layout | DELETE_DISCUSSION    |          128    |

|              985 | com.liferay.portal.model.Layout | PERMISSIONS            |          256     |

|              361 | com.liferay.portal.model.Layout | UPDATE                 |            4     |

|              986 | com.liferay.portal.model.Layout | UPDATE_DISCUSSION   |          512     |

|              362 | com.liferay.portal.model.Layout | VIEW                   |            1     |

 

Next, you can check how many different permission assignments have been given to pages in your portal:

select distinct actionIds from resourcepermission where name='com.liferay.portal.model.Layout';

 

In our case, a quick-view report will suffice where we let managers know which roles have default privileges (e.g. VIEW / Add Discussion) and which ones have elevated privileges and may need to be examined for appropriate access levels...

 

We can get the relevant data from the resourcepermissions table and calculate the presence of any bitwise value(s) we want to report on:
 

SELECT l.friendlyURL URL, 
l.type_ Page_Type,
r.name Role_Name,
(case when (actionIds < 4) then 'ViewDisc' else ‘Elevated’ end) Privileges
FROM resourcepermission rp
JOIN role_ r ON (rp.roleId = r.roleId)
JOIN layout l ON (rp.primKey = CAST(l.plid AS CHAR))
WHERE l.groupId=170790
AND r.name != 'Owner'
ORDER BY l.friendlyURL,r.name;

 

Creating Report Definitions and Reports:

The Jasper Reports engine will require an accurately-produced report definition .jrxml file, which can be created using the iReport Designer software that is available for Mac at this time. A JREE version is also available for Eclipse.
 

Install the iReport Designer.

Next, Use the QuickStart path and prepare a report definition file…:

  1. Create an ssh connection to target database server to allow the software to communicate with the database:
    ssh -l root -L 3308:localhost:3306 your.liferay.server.edu

  2. Create / test / save a JDBC connection (mysql) through the secure tunnel to the target db server that utilizes this tunnel:
             iReport-jdbc-conn.png

  3. Create a new report using the wizard. (I used the ‘Coffee’ [landscape] template)

  4. Insert your query. Choose “Next.” Enter your db password. This step may take a long time…

  5. Once it registers the query, Choose which fields will populate the report.

  6. Group BY (you may ignore this if you have an order by clause in your own query. Next. Finish

  7. Make size adjustments in the designer screen. I also removed the footer options, images and reduced the banner size dramatically, etc… deleted the footer band altogether. Set Title to 800, small font.

  8. In the XML View, swap out any Times or Arial fonts for “Serif” or “San-Serif”. Add the following property to your .jrxml file will prevent the header columns from repeating on each ‘page’ - this improved the formatting:
    <jasperReport ... isIgnorePagination="true" ...>

  9. Save the JRXML file.

  10. In Liferay, go to the desired site > Site Admin > Configuration > Reports Admin > Report Definitions > New Report Definition. Upload your .jrxml file and name / save it.

  11. Now choose Actions “Add a Report” from the newly-created report definition. Complete the form and save. A report will be generated shortly thereafter…

  12. Under Reports Admin, select your report. On the screen at the bottom is an Actions > Download option.
     

Report Definitions / Templates Created:

We have created several JRXML report templates which can quickly be adapted for any Site. The reports are stripped down to a minimalist presentation to allow for widest compatibility upon report export-- these reports are exporting perfectly for me as PDF, XLS, CSV, HTML. The following reports are ready-to-go:

  • Pages, Titles, Permissions, Roles, URLs

  • Web Content Objects / URLs, Roles, Permissions

  • Web Content Display portlets / URLs, Roles, Permissions

  • Document Library Folders, Roles, Permissions

  • Document Library Files, Roles, Permissions

  • IFrame Portlets, URLs, Roles, Permissions

  • Navigation Portlets, URLs, Roles, Permissions

 

I will share these files in an upcoming addition to this blog article.

Showing 6 results.
Items 20
of 1