We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 630
    • 39 Posts
    Hi modx/ditto experts,

    I want to gather two types of documents for an rss feed, which are, however, treated in slightly different ways:
    ($dateSource be here what is passed with the ditto parameter &dateSource
    $description be the document variable to be used for the feed item’s element `description`)


    • if a "changenote" (as tv; generally inspired by introtext, emphasizing what has changed recently) has been defined for the document, then $dateSource=’editedon’, $description = ’changenote’
    • else $dateSource=’createdon’, $description=’introtext’

    (How) can I do that?


    -- agilero;
      • 16278
      • 928 Posts
      Could be a job for PHx in your template, tied to a custom modifier (http://wiki.modxcms.com/index.php/PHx#Creating_a_custom_modifier).
      In your template:
      [+changenote:setDateSource+]

      In associated snippet called php:setDateSource, test $output (value of changenote) and use $modx->documentObject to retrieve document fields and make up the string you need as the return value.
        • 630
        • 39 Posts
        Nice idea, thank you.

        I’ll be surprised if that works, though. I reckon ditto will need to know dateSource before the template is rendered and filled for each entry, yet before it even knows which particular documents are at last to be displayed. So yes, I can predefine it with snippet parameter &dateSource=`editedon`, as this field is apparently initialized on/with document creation time (edit: sorry, my error! only if auto-publication is off, as in my case, so editedon is initially the date when I tick manually the option ’Publish’ in the document properties; before that it is undefined).
        Ex: Let’s say in a very early ditto execution phase document A is retrieved with a editedon=1242917600. As A lacks of a defined changenote, $A[’editedon’] is reset to createdon, which is a very old date though, in any case older that the createdon date of document B. Hence B would have to preceed A, but it had been excluded due to its editedon=1222613600 which is again older than any document retrieved for the current ditto call.

        In the end, it can work if the template is rendered before the ditto item’s date field (ruled by dateSource) is set. I’ll try and test that.
          • 16278
          • 928 Posts
          I tend to go for the "suck it and see" approach, myself. Surprisingly often, things just work. rolleyes KP
            • 630
            • 39 Posts
            I tend to go for the "suck it and see" approach, myself. Surprisingly often, things just work.
            Don’t get me wrong, I’m kind of a hobby scientist, and scientists like to make up hypotheses before testing something wink

            I just tested it: sad. Inside phx:setDateSource $modx->documentObject[’id’] is not the id of the document currently processed by ditto, but the feed document itself. So I pass the currently processed id with the $options to the phx modifier ...

            Ditto call:
            [[Ditto? &parents=`0` &dateSource=`editedon` &sortBy=`editedon`  &tpl=`rss_watched_item` &noResults=`<!-- keine Dokumente gefunden -->` &depth=`0` &display=`10` ]]


            rss_watched_item:
            <item>
            <title><![CDATA[ [+title+] ]]></title>
            <link>[(site_url)][~[+id+]~]</link>
            <description><![CDATA[ [+changenote:setDateSource=`[+id+]`+] ]]></description>
            <pubDate>[+editedon:date=`%a, %d %b %Y %H:%M:%S +0100`+]</pubDate>
            </item>


            phx:setDateSource:
            <?php
            $doc = isset($options) ? $modx->getDocument($options) : $modx->documentObject;
            if (!$output) {
                $doc['editedon'] = $doc['createdon'];
                return $doc['introtext'];
            }
            else return $output;
            ?>


            I guess where this approach fails: $doc is a normal php array that contains fresh, innocent database values that is neither about to be passed back to ditto, nor is accessed by ditto, which has therefore not the faintest idea of any reset values. To proove that I could be right:

            DocID 68 (private test document), with [*introtext*] set to "Zusammenfassung":
            changenote: [*changenote:ifempty=`(empty)`*]<br />
            rss_description=[*changenote:setDateSource*]<br />
            ditto_date=[*editedon*] <!-- from dateSource --> <br />
            createdon=[*createdon*]


            Output:
            changenote: (empty)
            rss_description=Zusammenfassung
            ditto_date=1242933118
            createdon=1224700652


            I think, Ditto is lacking a possibility of collecting a bunch of documents via multiple ditto calls of which the output is then suppressed. This bunch could be the retrieval basis of a final ditto call. A future version of ditto could be called like this to solve my problem:

            [[Ditto? &id=`xyz` &dateSource=`editedon` &filter=`changenote,,2` &appendOnly=`10` ]] <!-- get documents with set [*changenote*] -->
            [[Ditto? &id=`xyz` &dateSource=`createdon` &filter=`hidemenu,1,2` &appendOnly=`10` ]] <!-- other documents shown in menus -->
            [[Ditto? &id=`xyz` &display=`10` tpl=`rss_watched_item` ]] <!-- display items from both groups; will sort them anew no matter what date was -->
            


            Further comments appreciated,
            -- agilero.
              • 16278
              • 928 Posts
              Yes, you need to pass the retrieved documents’ IDs as the $options. And if you pass on a little more, you can call the phx modifier snippet twice, once for the text, once for the date, and insert the data where it’s needed, without trying to update the fields of the documents themselves.

              The template:
              <item>
              <title><![CDATA[ [+title+] ]]></title>
              <link>[(site_url)][~[+id+]~]</link>
              <description><![CDATA[ [+changenote:setDateSource=`desc,[+id+],[+introtext+]`+] ]]></description>
              <pubDate>[+changenote:setDateSource=`date,[+id+]`+]</pubDate>
              </item>

              The snippet:
              <?php
              $result="";
              
              $switches = explode(',',$options);
              
              $field = $switches[0];  // 'desc' = description, else date
              $docId = $switches[1];
              $intro = (isset($switches[2])) ? $switches[2] : "";
              
              $docInfo = $modx->getPageInfo($docId,1,'createdon,editedon');
              
              $created = $docInfo['createdon'];
              $edited = $docInfo['editedon'];
              
              if (!empty($output)) { 
              // item with changedate - return changedate text / editedon
                if ($field == 'desc') {
                 $result = $output;
                } else {
                 $result = strftime('%a, %d %b %Y %H:%M:%S +0100',$edited);
                }
              } else {
              // no change - return introtext / createdon
                if ($field == 'desc') {
                 $result = $intro;
                } else {
                 $result = strftime('%a, %d %b %Y %H:%M:%S +0100',$created);
                }
              }
              
              return $result;
              ?>

              This can be tidied to avoid calling the pageInfo() function twice for each item, but you get the idea.
              cool KP
                • 630
                • 39 Posts
                I’m afraid your suggestion is not quite that what I want.

                Your approach tries to solve my problem just on item template level. Hence it does not influence which particular documents are retrieved by ditto for then being shown. It does not touch Ditto’s notion of dateSource that provides seed for the underlying mysql queries.

                This is what I want to have done on doc-retrieval level:

                1.
                 SELECT sc.id, FROM_UNIXTIME( sc.editedon ) , tv.value
                FROM `site_content` AS sc
                LEFT JOIN site_tmplvar_contentvalues AS tv ON sc.id = tv.contentid
                WHERE tv.tmplvarid =13 -- tv changenote has id 13
                AND tv.value IS NOT NULL
                ORDER BY editedon DESC
                LIMIT 10;
                
                136  2009-05-21 17:37:46
                139 	2009-02-27 20:18:18
                135 	2009-02-02 21:51:11
                


                2.
                SELECT id, FROM_UNIXTIME( createdon )
                FROM `site_content`
                WHERE hidemenu =0
                ORDER BY createdon DESC
                LIMIT 10;
                
                id 	FROM_UNIXTIME(createdon)
                142 	2009-04-10 16:30:05
                141 	2009-04-10 16:25:22
                140 	2009-03-15 00:07:13
                139 	2009-02-22 19:44:00
                138 	2009-02-22 19:41:04
                137 	2009-02-08 00:27:20
                136 	2009-02-04 20:48:53
                135 	2009-02-02 21:30:37
                132 	2009-01-06 16:24:28
                131 	2008-12-14 00:04:24
                


                3. Now merge set2 into set1:
                id  <pubdate>          <description>
                136  2009-05-21 17:37:46 changenote
                142 	2009-04-10 16:30:05 introtext
                141 	2009-04-10 16:25:22 introtext
                140 	2009-03-15 00:07:13 introtext
                139 	2009-02-27 20:18:18 changenote
                138 	2009-02-22 19:41:04 introtext
                137 	2009-02-08 00:27:20 introtext
                135 	2009-02-02 21:51:11 changenote
                132 	2009-01-06 16:24:28 introtext
                131 	2008-12-14 00:04:24 introtext
                


                Looks like I’ll have to get my hands dirty coding a change-sensitive feed snippet from scratch. But first, I’ll study a bit the ditto source code, so, perhaps I decide to contribute the fetchOnly feature myself.

                Thank you for your ideas despite of my rejection smiley,

                -- agilero.
                  • 630
                  • 39 Posts
                  Here is my snippet. Your comments are always welcome.

                  <?php
                  /* A snippet to be used in the MODx Content Management System
                   * title: "Change notification feed"
                   * author: agilero
                   * version: 0.9-beta, tested with MODx 0.9.6.3
                   * 
                   * This snippet outputs (e.g.) rss-item chunks of docs which either
                   * - have been recently published (in this case, the item description is introtext)
                   * - or have been recently edited AND have got a change note set (description: value of the respective tv)
                   *
                   * NOTE: As soon as Ditto is capable of merging different results sets, this snippet may become obsolete.
                   *             Check the changelog of the latest Ditto version and rather prefer that awesome piece.
                   *
                   * Requirements: A template variable of type text to keep what's to be displayed
                   *          as intro for reannounced changed documents. If you don't want to have a
                   *          changed document reappear in the feed, leave the value empty.
                   *
                   * Installation: Nothing special. Refer to the general snippet installation guidelines
                   *
                   * Example Call (snippet label assumed here: ChangeNotifiier):
                   * {... any prefix code ... }
                   * [!ChangeNotifier? &ids=`5*` &tpl=`rss_watched_item` &limit=`10` !]
                   * { ... any suffix code ... }
                   *    &ids: either comma-separated list of ids (e.g. from Ditto) or one id with an asterisk denoting "this and all descendents"
                   *        if unset: whole site
                   *    &tpl: Omittable if you adjust the default value below. The chunk should contain the placeholders [+id+], [+title+], [+text+] and [+date+].
                   *        For debugging purposes there is [+set+] which is 'N' for "newly published" documents and 'C' for changed documents
                   *    &limit: default 10
                   *    &datefmt: defaults to a rss-compliant notation. Refer to the strftime() documentation to adapt it
                   *        TODO: Anybody knows how to enable phx support when parsing $tpl? So this parameter could be omitted in favour of phx:date ...
                   *
                   * Final Note: You should use change notifications only for significant, valuable changes on pages. Don't junk your user's feedreader
                   */ 
                  
                  /************** CONFIGURATION SECTION ********************/
                  $changenote_tv_id = 13; // as my tv "changenote" has the id 13 (shown on the resource manager / resources / template variables tab)
                  $title = 'longtitle'; // this is my choice. You'd rather use 'pagetitle', as this is mandatory at document creation, it cannot be empty -- 'longtitle' can.
                  
                  // adapt the default values here (after the colon, always quoted), so you can ommit those parameters in the call.
                  $tpl = isset($tpl) ? $tpl : 'rss_watched_item';
                  $limit = isset($limit) ? $limit : 10;
                  $datefmt = isset($datefmt) ? $datefmt : '%a, %d %b %Y %H:%M:%S +0100';
                  
                  /**************** PROGRAMMATIC SECTION (Changes on your risk of broken code ;-) ********************/
                  
                  global $modx;
                  
                  // 1. get the set of ids that can appear in the feed
                  if ( preg_match('/\d+\*$/', $ids) ) {
                      $ids = substr($ids, 0, -1);
                      $idset = $modx->getChildIds($ids);
                      array_unshift($idset,$ids);
                  }
                  elseif ( $ids != '*' ) { $idset = explode( ',', $ids); }
                  else { $idset = ''; }
                  
                  if ($idset) {
                      array_walk( $idset, 'pack_sql_cond');
                      $idset = '( ' . implode( ' OR ', $idset) . ' ) AND';
                  }
                  function pack_sql_cond (&$id, $index) { $id = 'sc.id='.$id; }
                  
                  $results = array();
                  
                  // 2. Retrieve documents recently edited AND with a changenote set
                  $res = $modx->db->select(
                      "sc.id id, sc.editedon date, sc.$title title, tv.value text",
                      "site_content sc, site_tmplvar_contentvalues tv",
                      "$idset sc.id = tv.contentid AND tv.tmplvarid=$changenote_tv_id AND tv.value IS NOT NULL",
                      "sc.editedon DESC",
                      $limit);
                  while ( $row = $modx->db->getRow($res) ) {
                      $row['set'] = 'C';
                      array_push($results, $row);
                  }
                  
                  // 3. Retrieve documents recently published and shown in the menus
                  $res = $modx->db->select(
                       "sc.id id, sc.publishedon date, sc.$title title, sc.introtext text",
                       "site_content sc",
                       "$idset sc.hidemenu=0",
                       "sc.publishedon DESC",
                       $limit);
                  while ( $row = $modx->db->getRow($res) ) {
                       $row['set'] = 'N';
                       array_push($results, $row);
                  }
                  
                  // 3. Resort the array.
                  // (Let's hope that, if an id occurs in both sets, the C-set entry occurs always first)
                  usort($results, "order_desc");
                  function order_desc ($a, $b) {
                      $a = $a['date']; $b = $b['date'];
                      if ($a == $b) {
                          return 0;
                      }
                      return ($a > $b) ? -1 : 1;
                  }
                  
                  // 4. Merge the results sets. Stop when limit is reached.
                  $keys = array();
                  foreach ( $results as $data ) {
                      $id = $data['id'];
                      if ($keys[$id]) continue;
                      if (!$limit--) break;
                      $keys[$id] = $data;
                      $keys[$id]['date'] = strftime($datefmt, $data['date']);
                  }
                  
                  // 5. Pack the results each into the template
                  $results = '';
                  foreach ( $keys as $doc )
                       $results .= $modx->parseChunk($tpl, $doc, '[+', '+]');
                  
                  return $results;
                  ?>