📜 ⬆️ ⬇️

Document Access System for MODX

Have a nice day, everyone.

I am writing at the moment for modx using individual documents for each regular user.
Since the admin panel did not want to score with these documents (if everything is fine with the site, there will be a lot of them there and the admin panel will start to slow down), I decided to put them into a separate database table, with output via one resource.

So, consider our requirements for the document (at least, I had these):
  1. The document should have a title and content;
  2. The document must have a type (for easier search of documents of the same type);
  3. The document owner always has access to edit and view it;
  4. The site owner and his lawyers have random access to any of the documents;
  5. The site owner and those to whom he has allowed it should be able to give the right users the right to view and edit an arbitrary document;
  6. An arbitrary registered user can get the right to only view or also edit an arbitrary document for a time or immediately forever.


1. Definition of data structure


First, let's estimate the structure of the document record in the database:
Structure of a specific document
// TryLoadDocument <?php /** ,         (   JSON   ): { "type":"agreement", -  , , , : carta, license  .. "title":"", -   "text":".  .  : ______ ", -  , XSS  CLeditor,   .    -     . "owner":29, -   ,   ,      .            (..  "") "edit":[29,555,34,52], -  ,    . !!   Administrator,Jurists     ! "view":[5677,599677,5999898677,855677] - ,    http://.../docs?doc_id=5   (   ) "view-temp":[{"id":5,"until":1413640050},{"id":9,"until":1413640100},{"id":7,"until":1413640050}] -  view,  "until"( timestamp) ,         (    ) "edit-temp":[{"id":5,"until":1413640050},{"id":9,"until":1413640100},{"id":7,"until":1413640050}] -  edit,  "until"( timestamp) ,         (    ) }       : CREATE TABLE IF NOT EXISTS `documents` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id ', `type` varchar(255) NOT NULL COMMENT ' ', `title` varchar(255) NOT NULL COMMENT ' ', `text` text NOT NULL COMMENT ' ', `owner` bigint(20) NOT NULL COMMENT ' ', `edit` text NOT NULL COMMENT ' ,        ', `edit-temp` text NOT NULL COMMENT '   ', `view` text NOT NULL COMMENT ' ,      ', `view-temp` text NOT NULL COMMENT '   ', UNIQUE KEY `id` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=cp1251 COMMENT='   - ...'; */ 


2. TryLoadDocument snippet


2.1 Ensuring portability

After determining the structure, it became clear that doing this set of functions would be a controversial decision, so the option with the PLO was chosen.
Well, at the same stage, we recall that objects are conveniently reused in other modules (snippets, in this case), so we use saving this snippet in a static docs.php file (which also makes it possible in the future to quickly connect the Ajax, if necessary ), and also add a check whether this snippet is connected to another just for the sake of our class in this way:
Check whether the snippet is used as the source of the Document class.
 <?php class Document {...} if(DOC_API_MODE!="API")//    Document,     ) { ...//    } 


Now to connect the Document class, write in another snippet:
')
 define("DOC_API_MODE","API"); include_once 'docs.php'; 


2.2 We write the general logic of the class

Now that all the preparations have been made, let's start working with the Document class itself.
First, we define the data in our document:

  private $data;//       ( $data['title'] -  ) private $uid;//=user id — id ,     ( ) private $modx;//   modx API 

In the constructor, we will only find out who loads us, and not what document he needs, which will allow, for example, using the same class object to create multiple documents to the user at once.

  public function __construct($modx) {//..    $modx   (    ),    $this->modx=$modx; $this->uid=$this->modx->user->get('id');//         } 

Next, let's estimate what we want the object of this class to give us? In other words, public methods.
Obviously, this:


Also add here what we want this class to do:

Here is the implementation of these functions:
Public methods
The simplest access to the fields outside the class:
  public function &__set ( $name , $value ) {//(  )    title  text $allowed=["title","text"]; if(in_array($name,$allowed)) { if($this->editAllowed())//     $this->data[$name]=$value; } } public function &__get ( $name ) {//          edit & view,   ... if($this->isOwner() || $this->viewAllowed() || $this->editAllowed()) { switch ($name) { case "title":return $this->data['title']; case "text": return $this->data['text']; case "id": return $this->data['id']; case "uid": return $this->uid; } } return 'forbidden'; } 

Now check permissions + create a new document from the specified data:
There is no work directly with the database yet.
  public function MakeNew($type,$title,$text)//    ,    { $this->data['text']=$text;//  $this->data['title']=$title;// $this->data['view']=[];//    $this->data['view-temp']=[]; $this->data['edit']=[];//    $this->data['edit-temp']=[]; $this->load($this->saveAsNew($type));//        } public function viewAllowed() {// ,       ,  - .. $allowed= $this->isOwner()//  -   || in_array($this->uid,$this->data['view']);//      if($allowed) return true; else for($i=0; $i<count($this->data['view-temp']);$i++) if($this->data['view-temp'][$i]->id==$this->uid) return $this->data['view-temp'][$i]->until > time(); return false; } public function editAllowed() {// ,       ,  - .. $allowed = $this->isOwner() || //  -   in_array($this->uid,$this->data['edit']);//     if($allowed) return true; else for($i=0; $i<count($this->data['edit-temp']);$i++) if($this->data['edit-temp'][$i]->id==$this->uid) return $this->data['edit-temp'][$i]->until > time(); //     '->' -    json_decode return false; } public function manageAllowed() {//true         . false   . return $this->modx->user->isMember('Jurists')||$this->modx->user->isMember('Administrator'); } public function allow($new_user,$can_edit,$time=0) { // -   . $new_user -  , //$can_edit -   (     ,   ) //$time -     (  0 - .    $user_id=(int)$new_user; if($user_id!=0 && $this->manageAllowed()) if($can_edit) { if($this->editAllowed()) { if($time==0)//   $this->data['edit'][]=$user_id; else//   $time  $this->data['edit-temp'][]=["id"=>$user_id,"until"=>time()+$time]; } } else { if($time==0)//   $this->data['view'][]=$user_id; else//   $time  $this->data['view-temp'][]=["id"=>$user_id,"until"=>time()+$time]; } } public function isOwner() { $usual=$this->uid==$this->data['owner'];//   : if($usual) return true;// , else//  return $this->manageAllowed(); } 

An auxiliary function that clears a document of obsolete temporary permissions:

  private function clearTemp() {//    e       if(count($this->data['view-temp'])+count($this->data['edit-temp']) > 0)//  -    { for($i=0; $i<count($this->data['view-temp']);$i++) //   ,       (time()) { if($this->data['view-temp'][$i]->until < time()) unset($this->data['view-temp'][$i]); } $this->data['view-temp']=array_values($this->data['view-temp']);//    ( [1,3,5]=>[0,1,2)] for($i=0; $i<count($this->data['edit-temp']);$i++) //   ,       (time()) { if($this->data['edit-temp'][$i]->until < time()) unset($this->data['edit-temp'][$i]); } $this->data['edit-temp'] = array_values($this->data['edit-temp']);//    ( [1,3,5]=>[0,1,2)] $this->save();//  } } 



2.3 We write functions for working with a specific database.

So, the logical part of our code, independent of a specific database, is complete. Now let's move to the level below, working directly with the database in MODX using PDO.
Work directly with the database
  public function load($id) {//   (     ,          ) $sql="SELECT * FROM `documents` WHERE `id`=:id"; $query = new xPDOCriteria($this->modx, $sql,array(':id'=>$id)); if($query->prepare() && $query->stmt->execute()) {//    $this->data = $query->stmt->fetchAll(PDO::FETCH_ASSOC)[0]; if(count($this->data)==0) return false;//   ,     $this->data['edit']=json_decode($this->data['edit']);///        $this->data['edit-temp']=json_decode($this->data['edit-temp']);///         $this->data['view']=json_decode($this->data['view']);///        $this->data['view-temp']=json_decode($this->data['view-temp']);///         $this->clearTemp();//  view-temp & edit-temp    return true;//  , ,    } else return false;//   ,     } public function save() {//      $sql="UPDATE `documents` SET `title`=:title, `text`=:text, `view`=:view, `edit`=:edit, `view-temp`=:viewtemp, `edit-temp`=:edittemp WHERE `id`=:id";//  $this->data['view']=json_encode($this->data['view']);///        $this->data['view-temp']=json_encode($this->data['view-temp']);///         $this->data['edit']=json_encode($this->data['edit']);///        $this->data['edit-temp']=json_encode($this->data['edit-temp']);///         $query=new xPDOCriteria($this->modx, $sql, [":title"=>$this->data['title'],":text"=>$this->data['text'],":edit"=>$this->data['edit'],":view"=>$this->data['view'],":edittemp"=>$this->data['edit-temp'],":viewtemp"=>$this->data['view-temp'],":id"=>$this->data["id"]]);//  $query->prepare() && $query->stmt->execute();//  //     $this->data['view']=json_decode($this->data['view']); $this->data['view-temp']=json_decode($this->data['view-temp']); $this->data['edit']=json_decode($this->data['edit']); $this->data['edit-temp']=json_decode($this->data['edit-temp']); } private function saveAsNew($type) {//        $type $sql="INSERT INTO `documents` (`title`,`text`,`view`,`edit`,`owner`,`type`) VALUES(:title,:text,:view,:edit,:uid,:type)";//  $this->data['view']=json_encode($this->data['view']);///        $this->data['edit']=json_encode($this->data['edit']);///        $this->data['view-temp']=json_encode($this->data['view-temp']);///        $this->data['edit-temp']=json_encode($this->data['edit-temp']);///        $query=new xPDOCriteria($this->modx, $sql, [":title"=>$this->data['title'],":text"=>$this->data['text'],":edit"=>$this->data['edit'],":view"=>$this->data['view'],":uid"=>$this->uid,":type"=>$type]);//  //   ,           //$this->modx->log(modX::LOG_LEVEL_ERROR," : ".$query->toSQL()); $query->prepare(); $query->stmt->execute();//  return $this->modx->lastInsertId();// id   } 


That's the whole Document class. Pretty simple, IMHO.

2.4 Executable Snippet Code

Now let's see what our TryLoadDocument snippet does?

So…
TryLoadDoc snippet executable code - parsing
We check if the anonymous user has come to us (his id is 0), in this case we send him to login.
 if($modx->user->get('id')==0)//    , { $modx->sendRedirect($modx->makeUrl(14));//     14,       . exit;//      ,   (     ) } 


We are trying to load the requested document (the resource where this snippet is used is / docs? Doc_id = N).
 $doc=new Document($modx);//           if(!$doc->load($_GET['doc_id']))//     {//  , ... return '  ...'; } 

We fill placeholders with values ​​that will allow us not to display further the forms for editing a document and editing the rights for it to those who cannot do it.
 //!!!   Rights,      $modx->setPlaceholder('CanManage',$doc->manageAllowed()?'true':'false'); //!!!   DocText,     $modx->setPlaceholder('CanEdit',$doc->editAllowed()?'true':'false'); 

Next is a simple function that parses what you entered as a user in the field of the rights editing form.
 function DataToUID($userstr,$modx)//        "" { $link_rgx="/(.*uid=)([\d]{1,})(.*)/";//    http://*   *?uid=56 $id_rgx="/([^\w]{0,})([\d]{1,})/";// id (      ) if(preg_match($link_rgx,$userstr)) {//   http://*   *?uid=56 $r="$2"; return preg_replace($link_rgx,$r,$userstr); } else if(preg_match($id_rgx,$userstr))//   " 234",  { $r="$2"; return preg_replace($id_rgx,$r,$userstr);//   } else//          ?    ? { $usr=$modx->getObject('modUser',["username"=>$userstr]);//      return $usr?$usr->get('id'):'-1'; } } 

We process adding rights, if there is one.
 if(isset($_POST['add']))//    "  ... { $userID=(int)DataToUID($_POST['userid'],$modx);// id     'userid' if($userID!='-1')//   { if($_POST['allow']=="edit")//       (   'allow') { //   $modx->log(modX::LOG_LEVEL_ERROR,"      #".$doc->id."  #".$doc->uid."  #".$userID); $doc->allow($userID,true,(int)$_POST['length']); $doc->save(); } else if($_POST['allow']=="view")////     (   'allow') { //   $modx->log(modX::LOG_LEVEL_ERROR,"      #".$doc->id."  #".$doc->uid."  #".$userID); $doc->allow($userID,false,(int)$_POST['length']); $doc->save(); } }else $modx->log(modX::LOG_LEVEL_ERROR,"DataToUID  . (".$_POST['userid'].")");//     ,    } 

Processing the document editing, if any
 if(isset($_POST['edit']))//      "" { $modx->log(modX::LOG_LEVEL_ERROR,"    #".$doc->id."  #".$doc->uid);//   if(!empty($_POST["text"]))//     (    ) { $doc->text=$_POST["text"];//   text   $doc->save(); $modx->log(modX::LOG_LEVEL_ERROR,"   #".$doc->id."  #".$doc->uid);//   } } 

We return to the placeholder [[+ doc]] the content of the resource, consisting of the document and the forms for editing it, which the user has access to.
 /**********   *************/ if(!isset($_POST['ajax']))//     ,    . { $output=""; // ,  ,     //   DocTitle   title,    .     -   . $output.=$modx->getChunk('DocTitle',['title'=>$doc->title]); //   DocText   title,    .     -   . $output.=$modx->getChunk('DocText',['text'=>$doc->text]); $modx->setPlaceholder('doc', $output); return ''; } 



For a better understanding, it may be more convenient for someone to look at the code in one block:
The entire snippet executable code of the TryLoadDocument
 if($modx->user->get('id')==0)//    , { $modx->sendRedirect($modx->makeUrl(14));//     14,       . exit;//      ,   (     ) } /*******/ $doc=new Document($modx);//           if(!$doc->load($_GET['doc_id']))//     {//  , ... return '  ...'; } //!!!   Rights,      $modx->setPlaceholder('CanManage',$doc->manageAllowed()?'true':'false'); //!!!   DocText,     $modx->setPlaceholder('CanEdit',$doc->editAllowed()?'true':'false'); function DataToUID($userstr,$modx)//        "" { $link_rgx="/(.*uid=)([\d]{1,})(.*)/";//    http://*   *?uid=56 $id_rgx="/([^\w]{0,})([\d]{1,})/";// id (      ) if(preg_match($link_rgx,$userstr)) {//   http://*   *?uid=56 $r="$2"; return preg_replace($link_rgx,$r,$userstr); } else if(preg_match($id_rgx,$userstr))//   " 234",  { $r="$2"; return preg_replace($id_rgx,$r,$userstr);//   } else//          ?    ? { $usr=$modx->getObject('modUser',["username"=>$userstr]);//      return $usr?$usr->get('id'):'-1'; } } /*********      ***********/ if(isset($_POST['add']))//    "  ... { $userID=(int)DataToUID($_POST['userid'],$modx);// id     'userid' if($userID!='-1')//   { if($_POST['allow']=="edit")//       (   'allow') { //   $modx->log(modX::LOG_LEVEL_ERROR,"      #".$doc->id."  #".$doc->uid."  #".$userID); $doc->allow($userID,true,(int)$_POST['length']); $doc->save(); } else if($_POST['allow']=="view")////     (   'allow') { //   $modx->log(modX::LOG_LEVEL_ERROR,"      #".$doc->id."  #".$doc->uid."  #".$userID); $doc->allow($userID,false,(int)$_POST['length']); $doc->save(); } }else $modx->log(modX::LOG_LEVEL_ERROR,"DataToUID  . (".$_POST['userid'].")");//     ,    } if(isset($_POST['edit']))//      "" { $modx->log(modX::LOG_LEVEL_ERROR,"    #".$doc->id."  #".$doc->uid);//   if(!empty($_POST["text"]))//     (    ) { $doc->text=$_POST["text"];//   text   $doc->save(); $modx->log(modX::LOG_LEVEL_ERROR,"   #".$doc->id."  #".$doc->uid);//   } } /**********   *************/ if(!isset($_POST['ajax']))//     ,    . { $output=""; // ,  ,     //   DocTitle   title,    .     -   . $output.=$modx->getChunk('DocTitle',['title'=>$doc->title]); //   DocText   title,    .     -   . $output.=$modx->getChunk('DocText',['text'=>$doc->text]); $modx->setPlaceholder('doc', $output); return ''; } return '...';//        ,     -    


3. Formatting of the received data and output to the page.


3.1 Required chunks

Three simple chunks are used to output the document correctly:
Rights - a form for editing rights
 <form action="[[~[[*id]]]]?doc_id=[[!GET? &param=`doc_id`]]" method="post" > <fieldset><span>  ...</span><input type="text" name="userid" style=" background: white; width: 340px; padding: 5px; font-size: 14px; margin: 0 15px; "/>  <select name="allow"> <option value="view" selected="selected"></option> <option value="edit">  </option> </select> <select name="length"> <option value="60"> 1 .</option> <option value="600"> 10 .</option> <option value="3600"> .</option> <option value="86400"> .</option> <option value="0">.</option> </select> <input type="hidden" name="add" value="1" /> </fieldset> <input type="submit" value=" !"/> </form> 


DocTitle - the template for the display title of the document.
 <h2>[[!+title]]</h2> 


DoctText * - document text template
 [[!If? &subject=`[[!+CanEdit]]` &operator=`EQ` &operand=`true` &then=`<form action="[[~[[*id]]]]?doc_id=[[!GET? &param=`doc_id`]]" method="post"> <input type="submit" value="   ..."/>`]] <textarea id="text" name="text" > [[+text]] </textarea> <script type="text/javascript"> CKEDITOR.replace( 'text' ); CKEDITOR.config.height=800; </script> <!--    CKeditor--> [[!If? &subject=`[[!+CanEdit]]` &operator=`EQ` &operand=`true` &then=`<input type="hidden" name="edit" value="1"/> <input type="submit" value="   ..."/> </form>`]] 


* - if for some reason the form is never displayed, you should check if the If component is installed?
And the simplest snippet to get a parameter from the address bar:
Get
 <? return isset($param)?$_GET[$param.""]:'0'; 


The final code of the TryLoadDocument snippet can be found here: pastebin.com/R55bPUCH
Well, almost everything is ready, it remains only to attach this to your content resource with your template.

3.2 Resource to work with the document

The code in the content field for the resource itself will be:

 [[!TryLoadDocument]] [[!If? &subject=`[[!+CanManage]]` &operator=`EQ` &operand=`true` &then=`[[$Rights]]`]] [[!+doc]] [[!If? &subject=`[[!+CanManage]]` &operator=`EQ` &operand=`true` &then=`[[$Rights]]`]] 

The textarea with the text of our document (from the DocText chunk) should be wrapped in a WYSIWYG editor, for example, I used CKeditor.
CKeditor put so
<head>*
  <script src="//cdn.ckeditor.com/4.4.5/full/ckeditor.js"></script> 

* — TV ExtraHeaders , <head> , , , TV. DocText textarea, ( ).

4. An example of using the Document class in another snippet


Suppose you want to create your documents in a different snippet for the current user:

 <?php ... define("DOC_API_MODE","API"); include_once 'docs.php'; ... $doc= new Document($modx); $title=""; $text=$modx->getChunk('agreement_template');//         //        -  $doc->MakeNew('agreement',$title,$text);//,    ... 


In this article, I tried to show how an idea goes into logic, then into code, and eventually into a working mechanism.

I hope someone will be useful.

I apologize in advance for errors in the text of the article (even though I tried to catch everything). I have no experience writing large texts. I would welcome any comments.

Source: https://habr.com/ru/post/241357/


All Articles