This component is designed to display files that are due for review upto 1 year away from today. The component will only provide a list of files, including how many days till the document review is due or how overdue it is. The component is very configurable allowing you to show documents to everyone with permissions, or just the reviewer (if specified) or just the owner, or both (again, if specified).
This article will explain how to configure and setup the document review component.
1. navigate into the folder intranet/common/classes
2. Create the file “TemplaterComponentDocumentDue.php” (keep in mind if you’re using a linux system, the name is case-sensetive), and place in it the following:
<?php /** * A component that is designed to show documents due for review * * @author Daniel Munn [daniel.munn@claromentis.com] */ require_once("../common/metadata_pkg.php"); class TemplaterComponentDocumentDue implements TemplaterComponent { const DEFAULT_UNIT_QUANTITY = 7; // Default is 7 days; const DEFAULT_MAX_RENDER = 20; // 20 documents max const FILE_MAX_LENGTH = 25; // Default maximum length of file names (will chop and display ... otherwise) const DUE_TYPE_REVIEWER = 1; const DUE_TYPE_OWNER = 2; const DUE_TYPE_ALL = 4; protected $data = array(); // Data storage protected $docs; // This just stores document information already pulled to prevent too much repetative information protected $html; //Rendering information protected $lasterror; //Output information; protected $attributes; //Attribute array protected $mode; // Mode of access (ALL | OWNER | REVIEWER); protected $metadate; // Metadata key (date) protected $metakey; //Metadata Key protected $displaymax; // Records to display; protected $datemax; //Maximum date we're checking against protected $datemin; //Today's date /** * Class constuctor */ public function __construct() { //With the constructor, we just ensure dates are pre-filled $this->datemax = new DateDay(); $this->datemin = new DateDay(); } /** * Attribute array values: * * metadate (str) - Metadata key to bind source to * * metareviewer (str) - metadata key for finding reviewer * * display (int) - [Optional - default 10 for time period specified] Number of document anniversary events to render * * timeframe (day/week/month) - [Optional - default day]Unit by which to retrieve information window * * noevents (str) - [Optional] Message to show if there are no events * * showtoall (bool) - Show all documents (in relevent permission groups) to persons instead of listing them specific to reviewer or owner * * hidetoowner (bool) - Hide documents meant for owner, can only be used when metareviewer is set; will suppress all documents that are intended for owner (even if they dont have a reviewer) - cannot be used in conjuntion with showtoall */ public function Show($attributes) { global $db; //Commit attributes to class $this->attributes = &$attributes; //Check Metakey Information if (!$this->CheckMetadata()) return $this->lasterror; //Output error, and hold execution //Attribute: Display - this will control how many birthdays are shown $this->CheckDisplay(); //Attribute: Timeframe - this will control how far forward (in time) this component will scope $this->CheckTimeFrame(); // Process metadata information including date processing - this passes information to Data; $this->ProcessMetadata(); //This is a continuation from the above method, we process the data into a html format $this->ProcessData(); //Return html processed by ProcessData; return $this->html; } /** * Check for an associated metakey and ensure its correct type (if required populate error) * @param str $metakey - Metakey to check for * @param int $metatype - Type of metadata we are looking for * @param bool $required - Is this a required metakey? * @return bool - Returns true if metakey exists and of correct type, false otherwise */ protected function CheckMetakey ($metakey, $metatype = META_TYPE_DATE, $required = true) { global $g_meta_field_factory; $dmfpobj = $g_meta_field_factory->GetPrototypeByKey($metakey); if (!$dmfpobj) //If object isnt instantiated loading failed and thus key does not exist { if ($required) $this->lasterror = $g_meta_field_factory->GetErrMsg(); return false; } if ($dmfpobj->IsRepeatable()) { if ($required) $this->lasterror = "The Metadata specified is marked as repeatable, which is wrong."; return false; } if ($dmfpobj->GetMetaType() != $metatype) { if ($required) $this->lasterror = "Metakey specified [${metakey}] holds an incorrect data type. [WANTED ${metatype}; GOT ".$dmfpobj->GetMetaType()."]"; return false; } else { return true; } //Now we check to see if meta owner is set } /** * Check and store information from the attribute: "metakey" * @param array $attributes - Pointer to attributes array; */ protected function CheckMetadata() { //Check the metadate input if (!$this->CheckMetakey($this->attributes['metadate'], META_TYPE_DATE)) return false; if ($this->attributes['showtoall'] == 'true') { $this->mode = self::DUE_TYPE_ALL; if (!$this->CheckMetaKey($this->attributes['metareviewer'], META_TYPE_USER)) { $this->mode = $this->mode | self::DUE_TYPE_OWNER; } } else { if (!$this->CheckMetaKey($this->attributes['metareviewer'], META_TYPE_USER)) { $this->mode = self::DUE_TYPE_OWNER; } else { $this->mode = self::DUE_TYPE_OWNER | self::DUE_TYPE_REVIEWER; } } if ($this->attributes['hidetoowner'] == 'true' && !($this->mode & self::DUE_TYPE_ALL) ) { if (! ( $this->mode & self::DUE_TYPE_REVIEWER )) { $this->html .= 'Unable to hide to owner when viewtype does not include reviewers'; } else if ( $this->mode & self::DUE_TYPE_OWNER ) $this->mode -= self::DUE_TYPE_OWNER; //Deduct DUE_TYPE_OWNER from mode of display } return true; } /** * Check and store information from the attribute: "display" * * @param array $attributes - Pointer to attributes array; */ protected function CheckDisplay () { $this->displaymax = self::DEFAULT_MAX_RENDER; if (isset($this->attributes['display'])) { if (is_numeric($this->attributes['display'])) { if ($this->attributes['display'] > 0) { $this->displaymax = $this->attributes['display']; //We have a valid positive integer, we commit that to maxrender } } } } /** * CheckTimeframe - checks and processes timeframe input from component * * @param array $attributes - Pointer to attributes array; */ protected function CheckTimeframe () { if (isset($this->attributes['timeframe'])) { @$this->datemax->modify('+'.$this->attributes['timeframe']); if (!$this->datemax->after($this->datemin)) $this->datemax->modify('+'.self::DEFAULT_UNIT_QUANTITY.' days'); } else { $this->datemax->modify('+'.self::DEFAULT_UNIT_QUANTITY.' days'); } } /** * ProcessMetadata - Recurse through stored information and places information in temporary data storage */ protected function ProcessMetadata() { global $db; require_once("../common/erms_pkg.php"); //We need this for ERMS access $this->data = array(); //Unset existing data [as we commit to session] $tcount = 0; // Count of records processed $pred_reviewer_key = 'me_' . $this->attributes['metareviewer']; $pred_review_key = 'me_' . $this->attributes['metadate']; $docs_list_provider = new DocsListProvider(); //We use the document list provider to actually do most of the hard work for us. $qp = new QueryPart(''); //Empty query part $qp_filled = false; if (! ($this->mode & self::DUE_TYPE_ALL) ) //The query we generate will automatically do this, we need to only add constraint if necessary. { if (! ( $this->mode & self::DUE_TYPE_OWNER ) && ($this->mode & self::DUE_TYPE_REVIEWER) ) //If they do not want the owner to see document { //This is something for reviewers but not owners - however we'll show to owners $qp = new QueryPart(" ( ( u.id = int:owner_id AND ${pred_reviewer_key}.intval = 0 ) OR ( u.id <> int:owner_id AND ${pred_reviewer_key}.intval = int:owner_id ) ) "); //Empty query part } elseif (! ( $this->mode & self::DUE_TYPE_REVIEWER ) && ($this->mode & self::DUE_TYPE_OWNER) ) { // No to reviewers (maybe not configured?) and where user is owner $qp = new QueryPart(" ( u.id = int:owner_id ) "); } else { //Document is either owned by owner, or to be reviewed by reviewer $qp = new QueryPart(" ( u.id = int:owner_id OR ${pred_reviewer_key}.intval = int:owner_id ) "); } $qp_filled = true; // Flag that we've done something } //Default bindings for above query parts $qp->Bind('owner_id', $_SESSION['SESSION_UID']); //Break the querypart into actionable sql $qp_query = $qp->AsPart(); if($qp_filled) $qp_query .= " AND "; // append AND to query part //I hate that I need to break this up the way I do if($this->attributes['metareviewer']) { $document_query = $docs_list_provider->GetSQLForDocuments("d.doc_id, d.title, dp.folder_id, u.id owner_id, ${pred_review_key}.intval doc_review_date, ${pred_reviewer_key}.intval doc_review_id, dc.title dc_title", array('u', 'dp', 'dc'), array($this->attributes['metareviewer'], $this->attributes['metadate']), null, null, true, " ORDER BY ${pred_review_key}.intval ASC ", $qp_query . " ${pred_review_key}.intval > 0 GROUP BY d.doc_id, d.doc_id, d.title, dp.folder_id, u.id, ${pred_review_key}.intval, ${pred_reviewer_key}.intval"); } else { $document_query = $docs_list_provider->GetSQLForDocuments("d.doc_id, d.title, dp.folder_id, u.id owner_id, ${pred_review_key}.intval doc_review_date, 0 doc_review_id, dc.title dc_title", array('u', 'dp', 'dc'), array($this->attributes['metadate']), null, null, true, " ORDER BY ${pred_review_key}.intval ASC ", $qp_query . " ${pred_review_key}.intval > 0 GROUP BY d.doc_id, d.doc_id, d.title, dp.folder_id, u.id, ${pred_review_key}.intval"); } $document_query->BindPart($qp); // Ensure bound values are transfered if ( $this->displaymax > 0 ) $document_query->setLimit($this->displaymax); $result = $db->query($document_query); while($row = $result->fetchArray()) { /*$tcount++; if ($tcount > $this->displaymax && $this->displaymax > 0) break; // Break out of while*/ //We need to re-structure the information comitted to array, give it a little more meaning; $row['doc_review_date'] = new DateDay($row['doc_review_date']); $datecalc = $row['doc_review_date']->toDays() - $this->datemin->toDays(); $this->data[$datecalc][] = $row; } } /** * ProcessData - Take information from temporary data store and render into an output format */ protected function ProcessData() { global $cfg_erms_user_friendly_urls; $this->html .= "<ul>"; if ( count($this->data) > 0 ) { ksort($this->data); //Lets order our array properly $rendered = 0; // Count the number that have been processed foreach ($this->data as $daynumber => $dayarray) //Cycle through array of data breaking into days and relevent array { if ($rendered >= $this->displaymax && $this->displaymax > 0) break; // Break out if we've reached max rendered foreach ($dayarray as $dayevent) //Break down array of events for specified day { //Now, we have broken information into a specific event; this is a rendering aspect of this loop //Conditional styling based on li class if($daynumber < 0) $li_open = '<li class="tcdd_overdue">'; //A document is overdue elseif ($daynumber <= 7) $li_open = '<li class="tcdd_due">'; //Document is due within the next week for review else $li_open = '<li>'; //Its a document that is due for review soon if ($rendered >= $this->displaymax && $this->displaymax > 0) break; // Break out if we've reached max rendered $this->html .= $li_open; //Information URL for document $dayevent['info_url'] = ERMSDataObj::GetDetailsUrlStatic($dayevent['doc_id'], $dayevent['folder_id']); //Retrieve the title of the document if dc_title, use that, if not use standard title. $document_title = ( strlen($dayevent['dc_title']) > 0 ) ? $dayevent['dc_title'] : $dayevent['title']; //Prepare url link based on information we know about. $dayevent['link_url'] = '/intranet/documents/' . $dayevent['folder_id'] . '/' . $dayevent['doc_id']. "/" . rawurlencode($document_title); // Build URL if ( $daynumber < 0 ) { $daynumber *= -1; $daylabel = ($daynumber <> 1) ? 'days' : 'day'; $daymessage = htmlentities("(${daynumber} ${daylabel} overdue)"); } else { $daylabel = ($daynumber <> 1) ? 'days' : 'day'; $daymessage = htmlentities("(${daynumber} ${daylabel})"); } if ( strlen($dayevent['title']) > self::FILE_MAX_LENGTH + 3) { $dayevent['title'] = substr($dayevent['title'], 0, self::FILE_MAX_LENGTH) . '...'; } $this->html .= sprintf('<a href="%s"><img src="/intranet/documents/images/info.gif" /></a> <a href="%s">%s</a><span> %s</span>', htmlentities($dayevent['info_url']), htmlentities($dayevent['link_url']), htmlentities($dayevent['title']), htmlentities($daymessage)); $rendered++; //Beancounting for maxrender $this->html .= '</li>'; } } } else { $this->html .= '<li>'; if (strlen($this->attributes['noevents']) > 0) { $this->html .= htmlentities($this->attributes['noevents']); } else { $this->html .= 'No upcoming events'; } $this->html .= "</li>"; } $this->html .= "</ul>"; } } ?>
Once completed, its time to configure this component: Firstly we’ll start by creating a user metadata information field
3. Paste the following component code anywhere on a templater file
<component class="TemplaterComponentDocumentDue" timeframe="1 year" metadate="doc_review_date" noevent="There are no documents due for review." showtoall="true" display="5">
Suggested location(s):
/interface_{custom}/main/intranet_home.html
or
/interface_{custom}/main/right_column.html
Discussion