From 3b66e188bfcd899b3265910b27f7117dcec873bb Mon Sep 17 00:00:00 2001
From: Patrick Barroca <pbarroca@afi-sa.fr>
Date: Thu, 30 Jun 2022 17:35:05 +0200
Subject: [PATCH] historic tracks rendering with new audio tracks object

---
 .../opac/controllers/NoticeajaxController.php |  20 +--
 library/Class/Notice/AudioTracks.php          | 135 +++++++++++----
 library/Class/Notice/AudioTracksVisitor.php   |  29 ++++
 library/ZendAfi/View/Helper/RenderTracks.php  | 156 ++++++++++++++++++
 .../Intonation/Library/Record/MediaHelper.php |   2 +-
 .../Intonation/View/RenderTracks.php          |   1 +
 .../NoticeAjaxControllerMorceauxTest.php      | 115 +++++++++++++
 .../AlbumAudioRecord/AlbumAudioRecordTest.php |  20 +--
 .../Templates/TemplatesMediaTest.php          |  86 ++++++++++
 9 files changed, 510 insertions(+), 54 deletions(-)
 create mode 100644 library/Class/Notice/AudioTracksVisitor.php
 create mode 100644 library/ZendAfi/View/Helper/RenderTracks.php
 create mode 100644 tests/application/modules/opac/controllers/NoticeAjaxControllerMorceauxTest.php

diff --git a/application/modules/opac/controllers/NoticeajaxController.php b/application/modules/opac/controllers/NoticeajaxController.php
index 849238a141a..b7923bcf15d 100644
--- a/application/modules/opac/controllers/NoticeajaxController.php
+++ b/application/modules/opac/controllers/NoticeajaxController.php
@@ -284,26 +284,26 @@ class NoticeAjaxController extends ZendAfi_Controller_Action {
 
   public function morceauxAction() {
     session_write_close();
+
     if (!$this->notice->isTypeDocSonore())
-      return false;
+      return $this->_ajaxResponseWithScript('');
 
-    // dans la notice
-    $morceaux=$this->notice->getMorceaux();
-    $source=$morceaux['source'];
+    $tracks = Class_Notice_AudioTracks::newFor($this->notice);
+    if ($tracks->hasContent())
+      return $this->_ajaxResponseWithScript($this->view->renderTracks($tracks));
 
     // Chez LastFm
-    if (!$morceaux["nb_resultats"]) {
-      $source="Last.fm";
-      $last_fm=new Class_WebService_Lastfm();
-      $morceaux=$last_fm->getMorceaux($this->notice->getTitrePrincipal(),
+    $source="Last.fm";
+    $last_fm = new Class_WebService_Lastfm();
+    $morceaux = $last_fm->getMorceaux($this->notice->getTitrePrincipal(),
                                       $this->notice->getAuteurPrincipal());
-    }
-
     $morceaux["id_notice"] = $this->notice->getId();
     if (!$morceaux["nb_resultats"])
       $source = '';
+
     $morceaux["auteur"] = $this->notice->getAuteurPrincipal();
     $html = $this->notice_html->getMorceaux($morceaux,$source);
+
     $this->_ajaxResponseWithScript($html);
   }
 
diff --git a/library/Class/Notice/AudioTracks.php b/library/Class/Notice/AudioTracks.php
index 01728cf9fa1..91f0466620d 100644
--- a/library/Class/Notice/AudioTracks.php
+++ b/library/Class/Notice/AudioTracks.php
@@ -20,15 +20,14 @@
  */
 
 
-/**
- * data source should / could be factorised with Class_Notice_TracksReader ?
- */
 abstract class Class_Notice_AudioTracks {
 
   protected Class_Notice $_record;
-  protected string $_zone = '';
+  protected string $_title_zone = '';
   protected string $_title_field = '';
-  protected string $_url_field = '';
+  protected string $_sample_zone = '';
+  protected string $_sample_title_field = '';
+  protected string $_sample_url_field = '';
   protected string $_source = '';
 
   protected ?Storm_Collection $_tracks;
@@ -54,38 +53,90 @@ abstract class Class_Notice_AudioTracks {
   }
 
 
+  public function acceptVisitor(Class_Notice_AudioTracksVisitor $visitor) : self {
+    $visitor
+      ->visitRecordId($this->_record->getId())
+      ->visitRecordAuthor($this->_record->getAuteurPrincipal())
+      ->visitSource($this->_source)
+      ->visitVolume(0);
+
+    $this->collection()
+         ->eachDo(fn($track) => $visitor->visitTrack($track));
+
+    return $this;
+  }
+
+
   public function collection() : Storm_Collection {
-    if ($this->_tracks)
-      return $this->_tracks;
+    return $this->_tracks ??= $this->_loadTracks();
+  }
 
 
-    return $this->_tracks = $this->_loadTracks();
+  public function withSampleCollection() : Storm_Collection {
+    return $this->collection()
+                ->select(fn($track) => $track->getUrl());
   }
 
 
   public function hasContent() : bool {
-    return $this->_zone && !empty($this->_record->get_subfield($this->_zone));
+    return $this->_title_zone && !empty($this->_record->get_subfield($this->_title_zone));
+  }
+
+
+  public function hasSample() : bool {
+    return $this->_sample_zone && !empty($this->_record->get_subfield($this->_sample_zone));
   }
 
 
   public function _loadTracks() : Storm_Collection {
-    return (new Storm_Collection($this->_record->get_subfield($this->_zone)))
+    $title_zones = $this->_zones($this->_title_zone)
+                        ->select(fn($zone) => $zone->firstValueOf($this->_title_field));
 
-      ->collect(fn($subfield) => $this->_record->get_zone($this->_zone, $subfield))
+    $sample_zones = ($this->_title_zone !== $this->_sample_zone)
+      ? ($this->_zones($this->_sample_zone)
+         ->select(fn($zone) => ($zone->firstValueOf($this->_sample_title_field)
+                                && $zone->firstValueOf($this->_sample_url_field))))
+      : null;
 
-      ->select(fn($zone) => ($zone->firstValueOf($this->_title_field)
-                             && $zone->firstValueOf($this->_url_field)))
+    $pairs = $title_zones
+      ->collect(fn($title_zone) => $this->_titleSampleZonesPairFor($title_zone, $sample_zones));
 
-      ->collect(fn($zone) => $this->_newTrack($zone));
+    return $pairs->collect(fn($pair) => $this->_newTrack($pair[0], $pair[1]));
   }
 
 
-  protected function _newTrack(Class_NoticeUnimarc_Zone $zone) : Class_Notice_AudioTrack {
+  protected function _titleSampleZonesPairFor(Class_NoticeUnimarc_Zone $title_zone,
+                                              ?Storm_Collection $sample_zones) : array {
+    if (null === $sample_zones)
+      return [$title_zone, $title_zone];
+
+    return [$title_zone,
+            ($sample_zones
+             ->detect(fn($sample_zone) => $this->_titleMatchSample($title_zone, $sample_zone)))];
+  }
+
+
+  protected function _titleMatchSample(Class_NoticeUnimarc_Zone $title_zone,
+                                       Class_NoticeUnimarc_Zone $sample_zone) : bool {
+    return $title_zone->firstValueOf($this->_title_field)
+      === $sample_zone->firstValueOf($this->_sample_title_field);
+  }
+
+
+  protected function _newTrack(Class_NoticeUnimarc_Zone $title_zone,
+                               ?Class_NoticeUnimarc_Zone $sample_zone) : Class_Notice_AudioTrack {
     return (new Class_Notice_AudioTrack)
-      ->setTitle($zone->firstValueOf($this->_title_field))
-      ->setUrl($zone->firstValueOf($this->_url_field))
+      ->setTitle($title_zone->firstValueOf($this->_title_field))
+      ->setUrl($sample_zone ? $sample_zone->firstValueOf($this->_sample_url_field) : '')
       ->setSource($this->_source);
   }
+
+
+  /** @return Storm_Collection of Class_NoticeUnimarc_Zone */
+  protected function _zones(string $name) : Storm_Collection {
+    return (new Storm_Collection($this->_record->get_subfield($name)))
+      ->collect(fn($subfield) => $this->_record->get_zone($name, $subfield));
+  }
 }
 
 
@@ -97,35 +148,39 @@ class Class_Notice_AudioTracksEmpty extends Class_Notice_AudioTracks {
   }
 
 
-  public function collection() : Storm_Collection {
-    return new Storm_Collection;
+  public function hasSample() : bool {
+    return false;
   }
-}
 
 
-
-
-class Class_Notice_AudioTracksBase extends Class_Notice_AudioTracks {
-  protected string $_zone = '464';
-  protected string $_title_field = 't';
-  protected string $_url_field = '3';
-  protected string $_source = '';
+  public function collection() : Storm_Collection {
+    return new Storm_Collection;
+  }
 }
 
 
 
 
 class Class_Notice_AudioTracksGam extends Class_Notice_AudioTracks {
-  protected string $_zone = '858';
+  protected string $_title_zone = '858';
   protected string $_title_field = 'z';
-  protected string $_url_field = 'u';
+  protected string $_sample_zone = '858';
+  protected string $_sample_title_field = 'z';
+  protected string $_sample_url_field = 'u';
   protected string $_source = 'GAM Annecy';
 
   public function __construct(Class_Notice $record) {
     parent::__construct($record);
 
-    $this->_zone = (new Storm_Collection(['958', '964', '985', '858']))
+    $this->_sample_zone = (new Storm_Collection(['958', '964', '985', '858']))
       ->detect(fn($zone) => $record->hasZone($zone) || '858' === $zone);
+
+    $this->_title_zone = (new Storm_Collection(['858', '464']))
+      ->detect(fn($zone) => $record->hasZone($zone) || '464' === $zone);
+
+    if ('464' == $this->_title_zone)
+      $this->_title_field = (new Storm_Collection(['t', 'a']))
+        ->detect(fn($field) => $record->get_subfield('464', $field));
   }
 }
 
@@ -133,8 +188,22 @@ class Class_Notice_AudioTracksGam extends Class_Notice_AudioTracks {
 
 
 class Class_Notice_AudioTracksCvs extends Class_Notice_AudioTracks {
-  protected string $_zone = '856';
-  protected string $_title_field = 'a';
-  protected string $_url_field = 'u';
+  protected string $_title_zone = '464';
+  protected string $_title_field = 't';
+  protected string $_sample_zone = '856';
+  protected string $_sample_title_field = 'a';
+  protected string $_sample_url_field = 'u';
   protected string $_source = 'CVS';
 }
+
+
+
+
+class Class_Notice_AudioTracksBase extends Class_Notice_AudioTracks {
+  protected string $_title_zone = '464';
+  protected string $_title_field = 't';
+  protected string $_sample_zone = '464';
+  protected string $_sample_title_field = 't';
+  protected string $_sample_url_field = '3';
+  protected string $_source = 'Bibliothèque';
+}
diff --git a/library/Class/Notice/AudioTracksVisitor.php b/library/Class/Notice/AudioTracksVisitor.php
new file mode 100644
index 00000000000..fab48e36ded
--- /dev/null
+++ b/library/Class/Notice/AudioTracksVisitor.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+interface Class_Notice_AudioTracksVisitor {
+  public function visitRecordId(int $id) : self;
+  public function visitRecordAuthor(string $author) : self;
+  public function visitSource(string $source) : self;
+  public function visitVolume(int $volume) : self;
+  public function visitTrack(Class_Notice_AudioTrack $track) : self;
+}
diff --git a/library/ZendAfi/View/Helper/RenderTracks.php b/library/ZendAfi/View/Helper/RenderTracks.php
new file mode 100644
index 00000000000..017234978eb
--- /dev/null
+++ b/library/ZendAfi/View/Helper/RenderTracks.php
@@ -0,0 +1,156 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_View_Helper_RenderTracks
+  extends ZendAfi_View_Helper_BaseHelper
+  implements Class_Notice_AudioTracksVisitor {
+
+  protected string $_source;
+  protected array $_volumes;
+  protected array $_tracks;
+  protected string $_video_img;
+  protected int $_record_id;
+  protected string $_record_author;
+
+
+  public function renderTracks(Class_Notice_AudioTracks $tracks) : string {
+    $this->_html = '';
+    $this->_volumes = [];
+    $this->_tracks = [];
+    $this->_video_img = Class_Profil::getCurrentProfil()->getUrlImage('bouton/voir_video.png');
+    $this->_record_id = 0;
+    $this->_record_author = '';
+
+    $tracks->acceptVisitor($this);
+    $this->_collectTracks();
+
+    return $this->_render();
+  }
+
+
+  public function visitRecordId(int $id) : self {
+    $this->_record_id = $id;
+    return $this;
+  }
+
+
+  public function visitRecordAuthor(string $author) : self {
+    $this->_record_author = $author;
+    return $this;
+  }
+
+
+  public function visitSource(string $source) : self {
+    $this->_source = $source;
+    return $this;
+  }
+
+
+  public function visitVolume(int $volume) : self {
+    return $this->_collectTracks();
+  }
+
+
+  public function visitTrack(Class_Notice_AudioTrack $track) : self {
+    $this->_tracks[] = $track;
+    return $this;
+  }
+
+
+  protected function _collectTracks() : self {
+    if ($this->_tracks)
+      $this->_volumes[] = $this->_tracks;
+
+    $this->_tracks = [];
+    return $this;
+  }
+
+
+  protected function _render() : string {
+    if (!$this->_volumes)
+      return '<table width="100%"><tr><td>'
+        . $this->_('Aucune information n\'a été trouvée')
+        . '</td></tr></table>';
+
+    $html = [];
+    foreach($this->_volumes as $k => $tracks)
+      $html[] = $this->_renderVolume($k, $tracks);
+
+    return ($this->_source
+            ? $this->_div(['class' => 'notice_info_titre'],
+                          $this->_tag('h3',
+                                      $this->_('Source : %s', $this->_source)))
+            : '')
+      .
+      implode($html);
+  }
+
+
+  protected function _renderVolume(int $volume, array $tracks) : string {
+    return
+      (1 < count($this->_volumes)
+       ? $this->_div(['class' => 'notice_info_ligne_titre'],
+                     $this->_('Volume n° %d', $volume+1))
+       : '')
+      .
+      $this->_tag('ul',
+                  implode(array_map(fn($pos, $track) => $this->_renderTrack($pos, $track, $volume),
+                                    array_keys($tracks),
+                                    $tracks)));
+  }
+
+
+  protected function _renderTrack(int $position, Class_Notice_AudioTrack $track, int $volume) : string {
+    $position++;
+    $volume++;
+
+    $id_div = $this->_record_id . "_" . $volume . "_" . $position;
+
+    $js_video = "chercher_videos('".$id_div."','".addslashes($this->_record_author)."','".addslashes($track->getTitle())."')";
+
+    $img_video = $this->view->tagImg($this->_video_img,
+                                     ['border' => '0',
+                                      'onclick' => $js_video,
+                                      'style' => 'cursor:pointer',
+                                      'title' => $this->_('Clip vidéo')]);
+
+    $img_close = $this->view->tagImg(URL_IMG.'bouton/contracter.gif',
+                                     ['border' => '0',
+                                      'onclick' => "afficher_media('".$id_div."','close','')",
+                                      'style' => 'cursor:pointer',
+                                      'title' => $this->_('Replier')]);
+
+    $img_ecoute = ($url = $track->getUrl())
+      ? $this->view->audioJsPlayer($url)
+      : '&nbsp;';
+
+    return $this
+      ->_tag('li',
+             $this->_div([], $img_video . $img_close)
+             . $this->_div(['class' => 'notice_info_ligne'],
+                           $position . ': ' . $track->getTitle())
+             . $img_ecoute
+             . $this->_div(['id' => $id_div,
+                            'rel' => 'video',
+                            'style' => 'display:none']));
+  }
+}
diff --git a/library/templates/Intonation/Library/Record/MediaHelper.php b/library/templates/Intonation/Library/Record/MediaHelper.php
index ac77105c0b3..298ebef48ef 100644
--- a/library/templates/Intonation/Library/Record/MediaHelper.php
+++ b/library/templates/Intonation/Library/Record/MediaHelper.php
@@ -49,7 +49,7 @@ class Intonation_Library_Record_MediaHelper {
         ->hasContent())
       return true;
 
-    if (Class_Notice_AudioTracks::newFor($this->_record)->hasContent())
+    if (Class_Notice_AudioTracks::newFor($this->_record)->hasSample())
       return true;
 
     return false;
diff --git a/library/templates/Intonation/View/RenderTracks.php b/library/templates/Intonation/View/RenderTracks.php
index c2d55dc0245..ff8ee342f8b 100644
--- a/library/templates/Intonation/View/RenderTracks.php
+++ b/library/templates/Intonation/View/RenderTracks.php
@@ -26,6 +26,7 @@ class Intonation_View_RenderTracks extends ZendAfi_View_Helper_BaseHelper {
 
 
   public function renderTracks(Storm_Collection $tracks) : string {
+    $tracks = $tracks->select(fn($track) => $track->getUrl());
     if ($tracks->isEmpty())
       return '';
 
diff --git a/tests/application/modules/opac/controllers/NoticeAjaxControllerMorceauxTest.php b/tests/application/modules/opac/controllers/NoticeAjaxControllerMorceauxTest.php
new file mode 100644
index 00000000000..d5eaec54f29
--- /dev/null
+++ b/tests/application/modules/opac/controllers/NoticeAjaxControllerMorceauxTest.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class NoticeAjaxControllerMorceauxTest extends AbstractControllerTestCase {
+  protected array $_titles = ['Nuit',
+                              'A quoi tu sers ?',
+                              'Il suffira d\'un signe',
+                              'Un, deux, trois',
+                              'Je commence demain',
+                              'Peurs',
+                              'Medley : Quand la musique est bonne/Au bout de mes rêves/Comme toi/Long is the road (Américain)/La vie par procuration/Pas toi',
+                              'Je l\'aime aussi',
+                              'Là-bas',
+                              'Vivre cent vies',
+                              'C\'est pas d\'l\'amour',
+                              'A nos actes manqués',
+                              'Je marche seul'];
+
+  protected array $_titles_with_sample = ['Nuit',
+                                          'A quoi tu sers ?',
+                                          'Il suffira d\'un signe',
+                                          'Un, deux, trois',
+                                          'Je commence demain'];
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture(Class_TypeDoc::class,
+                   ['id' => 8,
+                    'label' => 'mp3',
+                    'famille_id' => Class_CodifTypeDoc::SONORE]);
+
+    $unimarc = (new Class_NoticeUnimarc_Fluent)
+      ->zoneWithContent('001', '104363')
+      ->zoneWithChildren('200', ['a' => 'Sur scène',
+                                 'f' => 'Fredericks, Goldman, Jones'])
+      ->zoneWithChildren('710', ['a' => 'Fredericks, Goldman, Jones',
+                                 '4' => 590,
+                                 '6' => 'Interprète'])
+      ->zoneWithChildren('801', ['a' => 'FR', 'b' => 'GAM'])
+      ->zoneWithChildren('856', ['u' => 'https://www.gamannecy.com/upload/albums/202007/0888750979728_thumb.jpg',
+                                 'z' => 'Fredericks, Goldman, Jones / Sur scène'])
+
+      ;
+
+    foreach($this->_titles as $title)
+      $unimarc->zoneWithChildren('464', ['t' => $title]);
+
+    foreach($this->_titles_with_sample as $k => $title)
+      $unimarc->zoneWithChildren('958', ['u' => 'https://www.gamannecy.com/' . $k . '.mp3',
+                                         'z' => $title]);
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 18238,
+                    'type_doc' => 8,
+                    'unimarc' => $unimarc->render()]);
+
+    $this->dispatch('/opac/noticeajax/morceaux/id/18238');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsSourceGamAnnecy() {
+    $this->assertXPathContentContains('//h3', 'Source : GAM Annecy');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsAllTracks() {
+    $this->assertXPathCount('//li', count($this->_titles));
+  }
+
+
+  /** @test */
+  public function pageShouldContains5AudioPlayer() {
+    $this->assertXPathCount('//audio', 5);
+  }
+
+
+  public function tracksWithSample() {
+    return array_map(fn($k, $title) => [$k, $title],
+                     array_keys($this->_titles_with_sample),
+                     $this->_titles_with_sample);
+  }
+
+
+  /**
+   * @test
+   * @dataProvider tracksWithSample
+   */
+  public function pageShouldContainsAudioPlayerFor(int $position, string $title) {
+    $this->assertXPathContentContains('//li//div[@class="notice_info_ligne"][following-sibling::audio//source[@src="https://www.gamannecy.com/' . $position . '.mp3"]]',
+                                      ($position + 1) . ': ' . $title);
+  }
+}
diff --git a/tests/scenarios/AlbumAudioRecord/AlbumAudioRecordTest.php b/tests/scenarios/AlbumAudioRecord/AlbumAudioRecordTest.php
index 17061b35b04..c1f3671cc56 100644
--- a/tests/scenarios/AlbumAudioRecord/AlbumAudioRecordTest.php
+++ b/tests/scenarios/AlbumAudioRecord/AlbumAudioRecordTest.php
@@ -30,12 +30,12 @@ abstract class AlbumAudioRecordTestCase extends AbstractControllerTestCase {
   public function setUp() {
     parent::setUp();
 
-    $_http_client = Storm_Test_ObjectWrapper::mock()->whenCalled('open_url')->answers(null);
+    $_http_client = $this->mock()->whenCalled('open_url')->answers(null);
     Class_WebService_AllServices::setHttpClient($_http_client);
 
     Class_Notice::setTimeSource(new TimeSourceForTest('2014-01-19 09:00:00'));
 
-    $this->_codif_auteur_wrapper = Storm_Test_ObjectWrapper::onLoaderOfModel('Class_CodifAuteur');
+    $this->_codif_auteur_wrapper = $this->onLoaderOfModel(Class_CodifAuteur::class);
 
     Class_CosmoVar::newInstanceWithId('black_list_856', ['valeur' => 'mabib']);
     Class_CosmoVar::newInstanceWithId('unimarc_zone_titre',
@@ -48,11 +48,11 @@ abstract class AlbumAudioRecordTestCase extends AbstractControllerTestCase {
                            ->whenCalled('fetchAll')->answers([ [1, ''] ]);
     Zend_Registry::set('sql', $this->mock_sql);
 
-    $this->fixture('Class_CodifTypeDoc',
+    $this->fixture(Class_CodifTypeDoc::class,
                    ['id' => Class_TypeDoc::AUDIO_RECORD,
                     'famille_id' => Class_CodifTypeDoc::SONORE]);
 
-    $album = $this->fixture('Class_Album',
+    $album = $this->fixture(Class_Album::class,
                             ['id' => 4,
                              'type_doc_id' => Class_TypeDoc::AUDIO_RECORD,
                              'titre' => 'Seventh Son of a Seventh Son',
@@ -67,18 +67,18 @@ abstract class AlbumAudioRecordTestCase extends AbstractControllerTestCase {
 
     $album
       ->setDistributor('Geffen Records')
-      ->addRessource($this->fixture('Class_AlbumRessource',
+      ->addRessource($this->fixture(Class_AlbumRessource::class,
                                     ['id' => 1,
                                      'titre' => 'Moonchild',
                                      'fichier' => 'moonchild.mp3']))
-      ->addRessource($this->fixture('Class_AlbumRessource',
+      ->addRessource($this->fixture(Class_AlbumRessource::class,
                                     ['id' => 2,
                                      'titre' => 'Infinite Dreams',
                                      'fichier' => 'infinite_dreams.mp3']))
-      ->addRessource($this->fixture('Class_AlbumRessource',
+      ->addRessource($this->fixture(Class_AlbumRessource::class,
                                     ['id' => 3,
                                      'fichier' => 'unknown.mp3']))
-      ->addRessource($this->fixture('Class_AlbumRessource',
+      ->addRessource($this->fixture(Class_AlbumRessource::class,
                                     ['id' => 4,
                                      'fichier' => '502_05_the_prophecy.mp3']));
     $album->getMarc()
@@ -266,13 +266,13 @@ class AlbumAudioRecordViewNoticeTest extends AlbumAudioRecordTestCase {
 class AlbumAudioRecordViewMorceauxTest extends AlbumAudioRecordTestCase {
   public function setUp() {
     parent::setUp();
-    $this->dispatch('/opac/noticeajax/morceaux/id_notice/' . $this->_notice->getId(), true);
+    $this->dispatch('/opac/noticeajax/morceaux/id_notice/' . $this->_notice->getId());
   }
 
 
   /** @test */
   public function moonchildPlayerShouldBePresent() {
-    $this->assertXPath('//audio/source[contains(@src, "/bib-numerique/play-ressource/id/1.mp3")]', $this->_response->getBody());
+    $this->assertXPath('//audio/source[contains(@src, "/bib-numerique/play-ressource/id/1.mp3")]');
   }
 
 
diff --git a/tests/scenarios/Templates/TemplatesMediaTest.php b/tests/scenarios/Templates/TemplatesMediaTest.php
index 2e07a192497..e32739d6429 100644
--- a/tests/scenarios/Templates/TemplatesMediaTest.php
+++ b/tests/scenarios/Templates/TemplatesMediaTest.php
@@ -359,3 +359,89 @@ class TemplaitsMediaChiliRecordWithoutTracksTest extends TemplatesMediaTestCase
     $this->assertXPathCount('//div[@class="masonry-content"]//div[contains(@class, "empty_media")]', 5);
   }
 }
+
+
+
+
+class TemplatesMediaRecordWithBaseTracksWithoutSampleTest extends TemplatesMediaTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $unimarc = (new Class_NoticeUnimarc_Fluent)
+      ->zoneWithContent('001', '12345')
+      ->zoneWithChildren('200', ['a' => 'Psycho'])
+      ->zoneWithChildren('464', ['t' => 'Lotus out of water'])
+      ->zoneWithChildren('464', ['t' => 'Milking song'])
+      ->zoneWithChildren('700', ['a' => 'JM', 'b' => 'Big'])
+      ;
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 456,
+                    'unimarc' => $unimarc->render(),
+                    'type_doc' => 8,
+                    'clef_oeuvre' => 'PSYKO',
+                    'url_vignette' => 'big_picture_300x300.jpg',
+                    'facettes' => 'G13 M12']);
+  }
+
+
+  /** @test */
+  public function mediaPageShouldBeEmpty() {
+    $this->dispatch('/noticeajax/media/id/456');
+    $this->assertEmpty($this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function tracksPageShouldNotContainsLotusOutOfWater() {
+    $this->dispatch('/noticeajax/tracks/id/456');
+    $this->assertNotXPathContentContains('//div', 'Lotus out of water');
+  }
+
+
+  /** @test */
+  public function tracksPageShouldNotContainsMilkingSong() {
+    $this->dispatch('/noticeajax/tracks/id/456');
+    $this->assertNotXPathContentContains('//div', 'Milking song');
+  }
+}
+
+
+
+
+class TemplatesMediaRecordWithBaseTracksWithIncompleteSampleTest extends TemplatesMediaTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $unimarc = (new Class_NoticeUnimarc_Fluent)
+      ->zoneWithContent('001', '12345')
+      ->zoneWithChildren('200', ['a' => 'Psycho'])
+      ->zoneWithChildren('464', ['t' => 'Lotus out of water'])
+      ->zoneWithChildren('464', ['t' => 'Milking song',
+                                 '3' => 'https://samplemusic.org/1.mp3'])
+      ->zoneWithChildren('700', ['a' => 'JM', 'b' => 'Big'])
+      ;
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 456,
+                    'unimarc' => $unimarc->render(),
+                    'type_doc' => 8,
+                    'clef_oeuvre' => 'PSYKO',
+                    'url_vignette' => 'big_picture_300x300.jpg',
+                    'facettes' => 'G13 M12']);
+  }
+
+
+  /** @test */
+  public function pageShouldContainsMilkingSong() {
+    $this->dispatch('/noticeajax/media/id/456');
+    $this->assertXPathContentContains('//div', 'Milking song');
+  }
+
+
+  /** @test */
+  public function pageShouldNotContainsLotusOutOfWater() {
+    $this->dispatch('/noticeajax/media/id/456');
+    $this->assertNotXPathContentContains('//div', 'Lotus out of water');
+  }
+}
-- 
GitLab