Fixa ett index (instansräknare) för en nodtyp

Events happening in the community are now at Drupal community events on www.drupal.org.
lejonsson's picture

Jag har skapat en nodtyp för vilken jag vill ha ett fält som indexerar varje instans av noden på årsbasis, dvs fältet skall ges värdet som motsvarar antalet noder av denna typ som redan skapats under året plus ett. Årets första nod får nummer 1, den därefter 2, osv. Jag gissar att computed_field skulle kunna vara användbar, men jag vet inte om det är en bra idé eller hur det i så fall görs. Någon som har något förslag på tillvägagångssätt?

Comments

Vi har gjort precis det där

zoo33's picture

Vi har gjort precis det där på Filmbasen. Vi använde ett textfält skapat med CCK. Anledningen till att vi inte har ett numeriskt fält är att vi ville använda formatet "2009-001", men man hade också kunnat ha bara löpnumret i det fältet och automatiskt tagit hänsyn till året via nodens skapelsetid. (Eventuellt är det smartare.)

Vi byggde sen en modul som implementerar hook_nodeapi() och lägger in IDt i nya noder innan de sparas. Här är en generaliserad version:

<?php
/**
* Implementation of hook_nodeapi().
*/
function mymodule_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
 
// Create a yearly ID for Mytype nodes.
 
if ($node->type == 'mytype' && $op = 'presave') {
   
   
// Find the field's database info.
   
$field = content_fields('field_myfield', 'mytype');
   
$dbinfo = content_database_info($field);
   
   
// Run query to find the max value.
   
$query = "SELECT field_myfield_value FROM {" . $dbinfo['table'] . "} ORDER BY field_myfield_value DESC LIMIT 50}";
   
$res = db_query($query, $node->nid);
   
$year = date('Y');
   
$n = 0;
    while (
$prid = db_result($res)) {
      list(
$old_year, $old_n) = explode('-', $prid);
     
// Check that the value uses the proper pattern: YYYY-NNN.
     
if (empty($old_year) || (int) $old_year < 0 || empty($old_n) || (int) $old_n < 0) {
        continue;
      }
      else {
       
// This is the largest value returned that matches the pattern.
       
$year = (int) $old_year;
       
$n = (int) $old_n;
        break;
      }
    }
    if (
$year != date('Y')) {
     
// This will be the first record for this year.
     
$year = date('Y');
     
$n = 1;
    }
    else {
     
$n++;
    }
   
// Use three digits for the counter (works with up to 999 nodes yearly, then it breaks!).
   
$n = str_pad($n, 3, '0', STR_PAD_LEFT);
   
   
$node->field_myfield[0]['value'] = "$year-$n";
  }
}
?>

Ersätt myfield, mytype och mymodule med rätt namn.
Som sagt, den här koden är omskriven och plockad ur sitt sammanhang, och jag har inte testat den i den här formen. Men det kanske kan vara en lösning för dig om du är villig att pilla lite.

Du kan faktiskt använda i stort sett samma kod i ett computed field, men jag skulle rekommendera en modul istället.

/ Hannes Lilljequist – SthlmConnection

Rätta mig gärna om jag har

fabsor's picture

Rätta mig gärna om jag har fel men skulle man inte kunna tänka sig en variant där man bara gör nåt i stil med det här:

<?php
$current_year
= strtotime(date('Y'));
return
db_result(db_query("SELECT COUNT(*) AS noNodes FROM {node} WHERE type = '%s' AND created > %d",'nodetype',$current_year))+1;
?>

Jag har inte testat koden, men den visar i varje fall principen. Det här borde returnera antalet noder av den typen som skapats detta året. Det borde gå bra att anpassa den till computed field, att göra en modul för det lilla kanske är lite overkill ^^

//Fabian Sörqvist

Raderade noder

zoo33's picture

Enda kruxet med det är väl om noder raderas. Då kommer man plötsligt få två med samma ID. Men att använda created-fältet är absolut nåt man hade kunnat göra. Men man måste då också göra en "konvertering" mellan årtalet och created-tidsstämpeln för att det ska funka.

Som jag skrev ovan så är kanske en skolbokslösning på det här att man har ett numeriskt fält för IDt och sen kombinerar ihop det med created-fältet både när man presenterar IDt och när man tar reda på nästa värde i serien. Man hade kunnat göra typ:

SELECT MAX(field_myfield_val) FROM {xxxxxxx} LEFT JOIN {node} ON xxxxxxx WHERE node.created < xxxxxx AND node.created > xxxxxx

...och sen plussa på det med 1.

/ Hannes Lilljequist – SthlmConnection

En bit på väg...

lejonsson's picture

Tack för bra tips och råd. Jag måste erkänna att jag har en stor läxa att klara av vad gäller både php-syntax och mysql-interaktion, likväl som om hur Drupal fungerar under skalet. Det är något jag ska ta tag i framöver, men för den sajt som jag bygger är det just nu bara denna indexeringsfunktionalitet som jag behöver få till innan jag kan gå live, således hoppas jag kunna lyckas med det utan att först behöva sätta mig in i allt som jag egentligen borde förkovra mig i.

Jag har nu fått till en indexräknare för innehållstypen, koden som jag använder i computed field ser ut som följer:

if (!empty($node_field[0]['value'])) { // the node is not new
return $node_field[0]['value'];
}
else { // the node is new
$count = db_result(db_query("SELECT COUNT(*) from {node} WHERE type = 'dagbok'"));
$node_field[0]['value'] = $count;
}

Koden är delvis "lånad", men jag tycker mig iaf förstå den, nästan. Jag förstår att databasanropet räknar hur många träffar som fås i tabellen node där type är just denna nodtyp. Kan någon förklara hur det kommer sig att detta faktiskt ger +1 mot vad jag förväntar mig? När jag skapar tredje noden så levererar denna kod värdet 3, inte 2 fast det ju bara borde fås träff på två noder. Jättebra att koden ger vad jag vill ha, men jag vill gärna också förstå varför det blir som det blir.

Sen har jag ett problem som jag kört fast på efter att ha sökt och testat en massa. Koden ovan vill jag ju komplettera så att den begränsar räknandet till noder av den här typen som tillhör detta år. För mig är inte "skapad den"-året rätt uppgift att använda då noder kan skapas i efterhand för ett tidigare år (typiskt dagarna efter årsskiftet när dagböcker kan komma att skrivas för det redan avslutade året). Därför har jag i noden ett CCK Date-fält som heter field_datum (lagrat som datetime just nu, vet inte om det är klokt). Hur plockar jag ut årtalet ur detta fält och hur kompletterar jag databasfrågan ovan så att den begränsar till detta årtal?

Tacksam för vägledning!

Någon?

lejonsson's picture

Om någon ville förbarma sig över mig och mina frågor ovan skulle jag vara mycket tacksam, jag tycker mig så nära målet nu utan att lyckas nå ända fram. =(

Kan någon förklara hur det

zoo33's picture

Kan någon förklara hur det kommer sig att detta faktiskt ger +1 mot vad jag förväntar mig? När jag skapar tredje noden så levererar denna kod värdet 3, inte 2 fast det ju bara borde fås träff på två noder.

Jag är inte säker, men det borde betyda att noden redan är sparad i node-tabellen när din kod körs.

Här är förmodligen det du vill ha:

<?php

if (!empty($node_field[0]['value'])) { // the node is not new
 
return $node_field[0]['value'];
}
else {
// the node is new

  // Lättaste sättet att få ut året ur datumfältet är att ta det fyra första tecknen.
 
$year = substr($node->field_datum[0]['value'], 0, 4);

 
// Find the field's database info.
 
$field = content_fields('field_datum', 'dagbok');
 
$dbinfo = content_database_info($field);
 
 
// Kör databasfrågan.
  // Vi använder "field_datum_value LIKE '2009%'" för att exempelvis matcha alla datum från 2009.
 
$query = "SELECT COUNT(*) from {node} n LEFT JOIN {" . $dbinfo['table'] . "} f ON n.vid = f.vid WHERE n.type = 'dagbok' AND f.field_datum_value LIKE '%s'";
 
$count = db_result(db_query($query, $year . '%'));

 
$node_field[0]['value'] = $count;
}

?>

Har inte testat dock.

/ Hannes Lilljequist – SthlmConnection

Får inte ut innehållet i datumfältet

lejonsson's picture

Tack för hjälpen! Dock fungerar fortfarande inte begränsningen till år och felet sitter i utläsandet av datumfältet från den egna noden. Om jag själv sätter $year konstant till exempelvid '2008' i koden så fungerar det nämligen korrekt.

Jag testade att skriva ut $year men den är tom, vilket är precis vad jag fått även tidigare när jag labbat, jag lyckas inte komma åt datumet. Om jag skickar $node->field_datum[0]['value'] till någon funktion som förväntar sig en parameter med DateTime-format får jag felmeddelande likt "expects parameter 1 to be DateTime, null given", värdet tycks alltså vara null. Datumet i noden är som jag sagt tidigare ett CCK-Datetime-fält med namnet "field_datum", det återfinns (när jag kollar MySQL-databasen) i tabellen "content_field_datum" med värdeskolumnen "field_datum_value".

Någon idé om vad som är problemet? Hur kan jag gå vidare för att lista ut vad som blir fel?

Det fungerar, jag lät lura mig själv....

lejonsson's picture

Förklaring till varför jag inte fick till det hela:
För att kunna testa mig fram till önskad funktion så labbade jag med koden i en (av denna nodtyp) skapad nods brödtextfält. Det jag tog för givet var att jag även där skulle ha tillgång till $node och dess fält, men så tycks inte vara fallet. Databassökningen fungerade fint, men inte kodsnutten som via $node försökte läsa ut datumet. När jag väl satte in koden i Computed Field så fungerade det klockrent. Klantigt av mig, men kan någon säga något om vad man faktiskt har tillgång till om man skriver PHP-kod i brödtextfältet, kan man där komma åt nodens innehåll via tillgängliga variabler?

Tack för hjälpen, nu ska jag bygga ihop alla testade funktioner och gå live med min sajt!

Samma funktion, nu i D7

lejonsson's picture

Fyra år senare är det nu dags att uppgradera sajten med ovanstående kod i ett "Computed Field", och givetvis blev det för komplicerat igen för undertecknad.

Att ersätta $node_field[0]['value'] med $entity_field[0]['value'] fixar jag såklart, likaså att modifiera koden som plockar ut årtalet. Såhär långt har jag således kommit:

// Om detta är en redan existerande nod
if (!empty($entity_field[0]['value'])) {
  $entity_field[0]['value'] = $entity_field[0]['value'];
}

// Om detta är en ny nod
else {

  // Plocka ut året ur datumfältet genom att ta de fyra första tecknen
  $year = substr($entity->field_datum[LANGUAGE_NONE][0]['value'], 0, 4);

...

Men sen blir det värre och grunden till det är förstås att jag egentligen inte begriper vad originalkoden gör. Funktionerna content_fields och content_database_info finns inte längre, och när jag inte heller greppar vad $db_info egentligen innehåller så blir det tufft. Så långt känner jag att jag tappat bort mig, och likaså lär det finnas nya fällor i databasfrågan.

Följande kod för Drupal6 behöver således revideras, jag har gjort några tappra försök men inser att jag bara famlar.

  // Find the field's database info.
  $field = content_fields('field_datum', 'dagbok');
  $dbinfo = content_database_info($field);

  // Kör databasfrågan.
  // Vi använder "field_datum_value LIKE '2009%'" för att exempelvis matcha alla datum från 2009.
  $query = "SELECT COUNT(*) from {node} n LEFT JOIN {" . $dbinfo['table'] . "} f ON n.vid = f.vid WHERE n.type = 'dagbok' AND f.field_datum_value LIKE '%s'";
  $count = db_result(db_query($query, $year . '%'));

  $node_field[0]['value'] = $count+1;
}

År det någon (zoo33??) som skulle vilja förbarma sig över mig igen i detta ärende och hjälpa mig uppdatera den här kodsnutten så att den fungerar i d7?

Löst problemet...men...

lejonsson's picture

Jag har nu lyckats få koden att fungera, men jag hittade aldrig några funktionsanrop för att ersätta:

  // Find the field's database info.
  $field = content_fields('field_datum', 'dagbok');
  $dbinfo = content_database_info($field);

Kodade istället in tabellnamnet hårt, kan inte riktigt känna att det skulle vara ett problem. Intressant reflektion att man i fälttabellerna nu döpt om .vid till .revision_id, dock heter det fortfarande .vid i nodtabellen, rätt inkonsekvent kan jag tycka.

Sweden

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds:

Hot content this week