Xataface Tagger Module 0.3
Tagging Widget & Tag Cloud component for Xataface
tagger.php
Go to the documentation of this file.
00001 <?php
00002 /*
00003  * Xataface Tagger Module v 0.1
00004  * Copyright (C) 2011  Steve Hannah <steve@weblite.ca>
00005  * 
00006  * This library is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU Library General Public
00008  * License as published by the Free Software Foundation; either
00009  * version 2 of the License, or (at your option) any later version.
00010  * 
00011  * This library is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  * Library General Public License for more details.
00015  * 
00016  * You should have received a copy of the GNU Library General Public
00017  * License along with this library; if not, write to the
00018  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
00019  * Boston, MA  02110-1301, USA.
00020  *
00021  */
00032 class modules_tagger {
00033 
00040         private $baseURL = null;
00041         
00042         
00047         function __construct(){
00048                 $app = Dataface_Application::getInstance();
00049                 
00050                 // Register the beforeSave event handler to be called before any records
00051                 // in the system are saved.
00052                 $app->registerEventListener('beforeSave', array($this, 'beforeSave'));
00053                 
00054                 // Register the afterSave event handler to be called after any records
00055                 // are saved.
00056                 $app->registerEventListener('afterSave', array($this, 'afterSave'));
00057                 
00058                 // Register the initTransientField event handler which is called 
00059                 // when transient field data is loaded for the first time.
00060                 $app->registerEventListener('initTransientField', array($this, 'initField'));
00061                 
00062                 // Now work on our dependencies
00063                 $mt = Dataface_ModuleTool::getInstance();
00064                 
00065                 // We require the XataJax module
00066                 // The XataJax module activates and embeds the Javascript and CSS tools
00067                 $mt->loadModule('modules_XataJax', 'modules/XataJax/XataJax.php');
00068                 
00069                 
00070                 // Register the tagger widget with the form tool so that it responds
00071                 // to widget:type=tagger
00072                 import('Dataface/FormTool.php');
00073                 $ft = Dataface_FormTool::getInstance();
00074                 $ft->registerWidgetHandler('tagger', dirname(__FILE__).'/widget.php', 'Dataface_FormTool_tagger');
00075                 
00076                 
00077         }
00078         
00079         
00085         public function getBaseURL(){
00086                 if ( !isset($this->baseURL) ){
00087                         $this->baseURL = Dataface_ModuleTool::getInstance()->getModuleURL(__FILE__);
00088                 }
00089                 return $this->baseURL;
00090         }
00091         
00092         
00093 
00094 
00095         
00102         function getLabelColumn($field){
00103                 $labelCol = null;
00104                 if (  @$field['tagger_label']  ){
00105                         $labelCol = $field['tagger_label'];
00106                 } else {
00107                         $relationship = @$field['relationship'];
00108                         if ( !$relationship ){
00109                                 throw new Exception("Label column could not be found for field $field[name] because no relationship was specified.");
00110                                 
00111                         }
00112                         $table = Dataface_Table::loadTable($field['tablename']);
00113                         if ( PEAR::isError($table) ){
00114                                 throw new Exception($table->getMessage());
00115                         }
00116                         $relObj = $table->getRelationship($relationship);
00117                         
00118                         if ( PEAR::isError($relObj) ){
00119                                 throw new Exception($relObj->getMessage());
00120                         }
00121                         $domainTable = Dataface_Table::loadTable($relObj->getDomainTable());
00122                         if ( PEAR::isError($domainTable) ){
00123                                 throw new Exception($domainTable->getMessage());
00124                         }
00125                         $labelCol = $domainTable->guessField(
00126                                 array('varchar'=>10, 'char'=>8, 'enum'=>3, 'text'=>1),
00127                                 array('/name|title|value/'=>10, '/nom/'=>2)
00128                         );
00129                         
00130                 }
00131                 
00132                 if ( !$labelCol ){
00133                         throw new Exception("No label column could be found for the field ".$tfield['name'].".  Please specify a tagger_label directive.");
00134                         
00135                 }
00136                 return $labelCol;
00137         }
00138         
00139         
00147         function initField($event){
00148                 
00149                 //print_r($event->field);exit;
00150                 
00151                 if ( $event->field['widget']['type'] == 'tagger' ){
00152                         //die('here');
00153                         $relationship = @$event->field['relationship'];
00154                         if ( !$relationship ){
00155                                 $event->out = null;
00156                                 return;
00157                         }
00158                         $rrecs = $event->record->getRelatedRecordObjects($relationship, 'all');
00159                         $out = array();
00160                         try {
00161                                 $labelCol = $this->getLabelColumn($event->field);
00162                                 foreach ($rrecs as $rrec){
00163                                         $drec = $rrec->toRecord();
00164                                         $out[] = 'xfid://'.$drec->getId().' '.$drec->strval($labelCol);
00165                                         
00166                                         unset($drec);
00167                                 }
00168                                 $event->out = implode("\n", $out);
00169                                 return;
00170                         } catch ( Exception $ex){
00171                                 error_log($ex->getMessage());
00172                                 $event->out = null;
00173                                 return;
00174                         }
00175                         
00176                 } 
00177         }
00178 
00185         function beforeSave($params){
00186                 $record = $params[0];
00187                 if ( $record ){
00188                         
00189                         foreach ($record->_table->fields(false, true, true) as $fld){
00190                         
00191                                 // Go through all fields in the table to see if any of them
00192                                 // are tagger widgets.... we collect them all and record
00193                                 if ( @$fld['relationship'] and @$fld['widget']['type'] == 'tagger' and $record->valueChanged($fld['name']) ){
00194                                         // Only mark the field for handling if the widget:type=tagger
00195                                         // and a relationship is specified and the value has changed.
00196                                         if ( !@$record->pouch['tagger__fields'] ){
00197                                                 $record->pouch['tagger__fields'] = array();
00198                                         }
00199                                         
00200                                         // We store the fields in the record pouch so that we can access them'
00201                                         // in the afterSave handler
00202                                         $record->pouch['tagger__fields'][] = $fld['name'];
00203                                 }
00204                         }
00205                 }
00206         }
00207         
00208         
00221         function afterSave($params){
00222                 $record = $params[0];
00223                 $io = $params[1];
00224                 
00225                 // The tagger__fields array was populated in the beforeSave handler
00226                 // with the fields that have changed - and use the tagger widget.
00227                 if ( @$record->pouch['tagger__fields'] ){
00228                         foreach ($record->pouch['tagger__fields'] as $f){
00229                                 $tfield =& $record->_table->getField($f);
00230                                 
00231                                 $val = $record->val($f);
00232                                 
00233                                 $relationship = $record->_table->getRelationship($tfield['relationship']);
00234                                 $domainTable = Dataface_Table::loadTable($relationship->getDomainTable());
00235                                 if ( PEAR::isError($domainTable) ){
00236                                         return $domainTable;
00237                                 }
00238                                 
00239                                 $tval = explode("\n", $val);
00240                                 
00241                                 
00242                                 
00243                                 // Load existing records in the relationship
00244                                 $texisting =& $record->getRelatedRecordObjects($tfield['relationship'], 'all');
00245                                 if ( !is_array($texisting) or PEAR::isError($texisting) ){
00246                                         error_log('Failed to get related records for record '.$record->getId().' in its relationship '.$tfield['relationship']);
00247                                         unset($tval);
00248                                         unset($orderCol);
00249                                         unset($tval_new);
00250                                         unset($torder);
00251                                         unset($trelationship);
00252                                         unset($tval_existing);
00253                                         unset($relationship);
00254                                         unset($domainTable);
00255                                         continue;
00256                                 }
00257                                 //$texistingIds = array();
00258                                 $texistingLabels = array();
00259                                 $labelMap = array();
00260                                 $labelCol = $this->getLabelColumn($tfield);
00261                                 
00262                                 if ( !$labelCol ){
00263                                         throw new Exception("No label column could be found for the field ".$tfield['name'].".  Please specify a tagger_label directive.");
00264                                         
00265                                 }
00266                                 
00267                                 
00268                                 foreach ($texisting as $terec){
00269                                         $drec = $terec->toRecord();
00270                                         $texistingLabels[$drec->getId()] = trim($terec->val($labelCol));
00271                                         $labelMap[$drec->getId()] = $terec;
00272                                         unset($drec);
00273                                         
00274                                 }
00275                                 
00276                                 $del = $record->_table->getDelegate();
00277                                 $addMethod = $f.'__addTag';
00278                                 
00279                                 
00280                                 
00281                                 // Load currently checked records
00282                                 
00283                                 // Now we have existing ids in $texistingIds
00284                                 // and checked ids in $tcheckedIds
00285                                 
00286                                 // See which records we need to have removed
00287                                 $texistingLabels = array_map('trim', $texistingLabels);
00288                                 $tval = array_map('trim', $tval);
00289                                 $temp = array();
00290                                 foreach ($tval as $k=>$v){
00291                                         if ( preg_match('/^xfid:\/\/([^ ]+) (.*)$/', $v, $matches) ){
00292                                                 $temp[$matches[1]] = $matches[2];
00293                                         } else {
00294                                                 
00295                                                 $drec = df_get_record($domainTable->tablename, array($labelCol=>'='.$v));
00296                                                 if ( !$drec ){
00297                                                         // the record isn't created yet.. so let's create it
00298                                                         if ( isset($del) and method_exists($del, $addMethod) ){
00299                                                                 $drec = $del->$addMethod($record, $tid);
00300                                                         } else {
00301                                                                 $drec = new Dataface_Record($domainTable->tablename, array());
00302                                                                 $drec->setValue($labelCol, $v);
00303                                                         }
00304                                                         if ( !$record->checkPermission('add new related record', array(
00305                                                                 'relationship' => $tfield['relationship']
00306                                                         ))){
00307                                                                 $res = PEAR::raiseError("Failed to add tag '$val' because you don't have permission to add new related records to this relationship.");
00308                                                                 
00309                                                         } else {
00310                                                                 $res = $drec->save();
00311                                                         }
00312                                                         
00313                                                         if ( PEAR::isError($res) ){
00314                                                                 return PEAR::raiseError('Failed save tags for field "'.$tfield['widget']['label'].'" because the tag "'.$v.'" could not be saved.  The error while trying to save the tag was: '.$res->getMessage(), DATAFACE_E_NOTICE);
00315                                                                 
00316                                                         }
00317                                                 }
00318                                                 $temp[$drec->getId()] = $v;
00319                                                 
00320                                                 unset($drec);
00321                                         }
00322                                 }
00323                                 $tval = $temp;
00324                                 
00325                                 
00326                                 $existingIds = array_keys($texistingLabels);
00327                                 $newIds = array_keys($tval);
00328                                 
00329                                 $tremoves = array_diff($existingIds, $newIds);
00330                                 $tadds = array_diff($newIds, $existingIds);
00331                                 
00332                                 //$tremoves = array(); //array_diff($texistingLabels, $tval);
00333                                 //$tadds = array();     //array_diff($tval, $texistingLabels);
00334                                 
00335                                 
00336                                 
00337                                 foreach ($tremoves as $tid){
00338                                         $trec = $labelMap[$tid];
00339                                         $res = $io->removeRelatedRecord($trec, false, $secure);
00340                                         if ( PEAR::isError($res) ) return $res;
00341                                         unset($trec);
00342                                 }
00343                                 
00344                                 
00345                                 foreach ($tadds as $tid){
00346                                         $drec = df_get_record_by_id($tid);
00347                                         if ( !$drec ){
00348                                                 
00349                                                 return PEAR::raiseError('Failed to add tag '.$tid.' because it could not be found.');
00350                                                 
00351                                         }
00352                                 
00353                                         //$trecvals = $checkedId2ValsMap[$tid];
00354                                         $trec = new Dataface_RelatedRecord($record, $tfield['relationship'], $drec->vals());
00355                                         
00356                                         $res = $io->addExistingRelatedRecord($trec, $secure);
00357                                         if ( PEAR::isError($res) ) return $res;
00358                                         unset($drec, $trec);
00359                                 }
00360                                 
00361                                 unset($tadds);
00362                                 unset($tremoves);
00363                                 unset($tcheckedIds, $tcheckedId2ValsMap);
00364                                 unset($tcheckedRecords);
00365                                 unset($tchecked);
00366                                 unset($texistingIds);
00367                                 unset($texisting);
00368                                         
00369                         }
00370                         
00371                         unset($record->pouch['tagger__fields']);
00372                 }
00373         }
00374         
00383         private function formRequiresTagger(&$form){
00384                 
00385                 if ( @$form['elements'] and is_array($form['elements']) ){
00386                         foreach ($form['elements'] as $e ){
00387                                 if ( @$e['field']['widget']['type'] == 'tagger' ){
00388                                         return true;
00389                                 }
00390                         }
00391                 }
00392                 
00393                 if ( @$form['sections'] and is_array($form['sections']) ){
00394                         foreach ($form['sections'] as $s ){
00395                                 if ( $s['elements'] and is_array($s['elements']) ){
00396                                         foreach ($s['elements'] as $e ){
00397                                                 if ( @$e['field']['widget']['type'] == 'tagger' ){
00398                                                         return true;
00399                                                 }
00400                                         }
00401                                 }
00402                         }
00403                 }
00404                 
00405                 return false;
00406                 
00407         }
00408         
00421         function block__after_form_open_tag($params=array()){
00422         
00423                 $form = $params['form'];
00424                 if ( !$this->formRequiresTagger($form) ){
00425                         return null;
00426                 }
00427                 
00428                 $jt = Dataface_JavascriptTool::getInstance();
00429                 $jt->addPath(dirname(__FILE__).'/js', $this->getBaseURL().'/js');
00430                 
00431                 $ct = Dataface_CSSTool::getInstance();
00432                 $ct->addPath(dirname(__FILE__).'/css', $this->getBaseURL().'/css');
00433                 
00434                 // Add our javascript
00435                 $jt->import('xataface/widgets/tagger.js');
00436                 
00437         }
00438         
00449         function getTagCloudFields($fields){
00450                 $out = array();
00451                 foreach ($fields as $k=>$f){
00452                         if ( @$f['tag_cloud'] ){
00453                                 $out[$k] =& $f;
00454                         }
00455                         unset($f);
00456                 }
00457                 return $out;
00458         }
00459         
00460         
00472         function drawTransientTagCloud($field){
00473                 $table = Dataface_Table::loadTable($field['tablename']);
00474                 $relationship = $table->getRelationship($field['relationship']);
00475                 try {
00476                         $labelCol = $this->getLabelColumn($field);
00477                 } catch (Exception $ex){
00478                         return;
00479                 }
00480                 
00481                 $sql = $relationship->getSQL();
00482                 
00483                 $frompos = strpos(strtolower($sql), ' from ');
00484                 //echo "Frompos $frompos";
00485                 $select = 'select count(*) as num, `'.$labelCol.'`';
00486                 $newsql = $select.' '.substr($sql, $frompos);
00487                 $newsql = preg_replace('/`([^`]+)` ?= ?\'\$[^\']+\'/', '`$1` is not null', $newsql);
00488                 $newsql .= ' group by `'.$labelCol.'` limit 50';
00489                 //echo $newsql;
00490                 $res = mysql_query($newsql, df_db());
00491                 if ( !$res ) throw new Exception(mysql_error(df_db()));
00492                 $jt = Dataface_JavascriptTool::getInstance();
00493                 $jt->addPath(dirname(__FILE__).'/js', $this->getBaseURL().'/js');
00494                 
00495                 $ct = Dataface_CSSTool::getInstance();
00496                 $ct->addPath(dirname(__FILE__).'/css', $this->getBaseURL().'/css');
00497                 
00498                 // Add our javascript
00499                 $jt->import('tagcloud.js');
00500                 
00501                 echo '<div class="xf-tagcloud">';
00502                 echo '<h2>'.htmlspecialchars($field['widget']['label']).'</h2>';
00503                 echo '<ul>';
00504                 while ($row = mysql_fetch_row($res) ){
00505                         $link = df_absolute_url(DATAFACE_SITE_HREF.'?-action=list&-table='.$field['tablename'].'&'
00506                                 .urlencode($field['relationship'].'/'.$labelCol).'='.urlencode('='.$row[1]));
00507                         
00508                 
00509                         echo '<li data-xf-frequency="'.$row[0].'"><a href="'.htmlspecialchars($link).'">'.htmlspecialchars($row[1]).'</a></li>';
00510                 }
00511                 echo '</ul>
00512                 
00513                 </div>';
00514                 
00515                 //echo '['.$sql.']';
00516         
00517         }
00518         
00519         function drawScalarTagCloud($field){}
00520         
00521         
00531         function drawTagCloud($field){
00532                 if ( @$field['transient'] and @$field['relationship'] ){
00533                         $this->drawTransientTagCloud($field);
00534                 } else if (!@$field['transient'] ){
00535                         $this->drawScalarTagCloud($field);
00536                 }
00537                 
00538         }
00539         
00543         function block__after_left_column(){
00544                 
00545                 $app = Dataface_Application::getInstance();
00546                 $query = $app->getQuery();
00547                 $table = Dataface_Table::loadTable($query['-table']);
00548                 $fields = $this->getTagCloudFields($table->fields(false,true,true));
00549                 
00550                 
00551                 foreach ($fields as $k=>$v){
00552                         $this->drawTagCloud($v);
00553                 }
00554                 
00555         }
00556 }
 All Data Structures Files Functions