Xataface Tagger Module 0.3
Tagging Widget & Tag Cloud component for Xataface
|
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 }