'libelle']); array_unshift($all_bibs, $this->getPortail()); return $all_bibs; } public function findAllByWithPortail($args) { $bibs = $this->findAllBy($args); array_unshift($bibs, $this->getPortail()); return $bibs; } public function findAllByIdZone($id_zone = 0) { $id_zone = (int)$id_zone; if (!$id_zone) return Class_Bib::findAllBy(['order' => 'ville']); return Class_Bib::findAllBy(['id_zone' => $id_zone, 'order' => 'ville']); } public function getUserFriendlyBibs() { $all_bibs = Class_Bib::getLoader()->findAllBy(['order' => 'libelle']); $bibs_as_array = []; foreach($all_bibs as $bib) { $bibs_as_array[$bib->getId()] = $bib->getLabel(); } return $bibs_as_array; } public function getPortail() { if (!isset($this->_portail)) $this->_portail = $this->newInstanceWithId(0)->setLibelle($this->_('Portail')); return $this->_portail; } public function getBibsWithMail() { $all_bibs = Class_Bib::findAll(); $all_bibs = array_filter($all_bibs, function($bib) {return $bib->hasMail() ? $bib->getLibelle(): '';}); $libelles = []; foreach($all_bibs as $bib) { $libelles[$bib->getId()] = $bib->getLibelle(); } return $libelles; } public function areAllBibsSelected($ids) { if(!is_array($ids)) $ids = explode(',',$ids); $all_bibs = Class_Bib::findAllBy(['visibilite' => Class_Bib::V_DATA]); $all_bibs_ids = []; foreach($all_bibs as $bib) $all_bibs_ids[] = $bib->getId(); sort($ids); sort($all_bibs_ids); return $ids == $all_bibs_ids; } public function getSelectedZones($ids) { if(!is_array($ids)) $ids = explode(',',$ids); $zones = Class_Zone::findAll(); $bibs_by_zones = []; foreach($zones as $zone) { $bibs_by_zones[$zone->getId()] = Class_Bib::findAllBy(['id_zone' => $zone->getId(), 'visibilite' => Class_Bib::V_DATA]); } $selected_zones = []; $select_bibs_not_in_zone = []; foreach($bibs_by_zones as $id => $bibs) { if(!$ids || !$bibs) continue; $bibs_ids = []; foreach($bibs as $bib) $bibs_ids[] = $bib->getId(); if(0 == count(array_diff($bibs_ids, $ids))) { $selected_zones[] = $id; $ids = array_diff($ids, $bibs_ids); } } return [$selected_zones, $ids]; } public function findAllWithRedmine() { return array_filter(array_unique(array_merge(Class_Bib::findAllBy(['redmine_login not' => '', 'redmine_password not' => '']), Class_Bib::findAllBy(['redmine_api_key not' => ''])))); } public function findAllLabels() { return static::byIdLabels(Class_Bib::findAll()); } public function byIdLabels($librairies) { $labels = []; foreach ($librairies as $library) $labels[$library->getId()] = $library->getLibelle(); return $labels; } public function findAllWithCoordinates() { return Class_Bib::filterByCoordinates(Class_Bib::findAll()); } public function filterByCoordinates($libraries) { return (new Storm_Model_Collection($libraries)) ->select('hasLatitude') ->select('hasLongitude') ->getArrayCopy(); } } class Class_Bib extends Storm_Model_Abstract { use Trait_PermissionTargetable, Trait_CustomFields, Trait_Translator, Trait_StaticFileSystem, Trait_TimeSource; const V_INVISIBLE = 0, V_NODATA = 1, V_DATA = 2, UPLOAD_MAX_SIZE = 250, STATE_OPENED = 'opened', STATE_OPENED_NOW = 'opened_now', BASE_PATH = 'photobib/', THUMB_PREFIX = 'thumb_', CODE_FACETTE = 'B'; protected static $_default_image_factory; private $_dataBaseError = "Problème d'accès à la base de données", $statut_bib; protected $_loader_class = 'BibLoader', $_table_name = 'bib_c_site', $_table_primary = 'ID_SITE', $_has_many = ['profils' => ['model' => 'Class_Profil', 'role' => 'bib', 'dependents' => 'delete'], 'users' => ['model' => 'Class_Users', 'role' => 'bib', 'dependents' => 'delete'], 'article_categories' => ['model' => 'Class_ArticleCategorie', 'role' => 'bib', 'scope' => ['ID_CAT_MERE' => 0], 'order' => 'libelle', 'dependents' => 'delete'], 'articles' => ['through' => 'article_categories'], 'sitotheque_categories' => ['model' => 'Class_SitothequeCategorie', 'role' => 'bib', 'scope' => ['ID_CAT_MERE' => 0], 'order' => 'libelle', 'dependents' => 'delete'], 'sitotheques' => ['through' => 'sitotheque_categories'], 'rss_categories' => ['model' => 'Class_RssCategorie', 'role' => 'bib', 'scope' => ['ID_CAT_MERE' => 0], 'order' => 'libelle', 'dependents' => 'delete'], 'feeds' => ['through' => 'rss_categories'], 'ouvertures' => ['model' => 'Class_Ouverture', 'role' => 'bib', 'order' => [ 'jour', 'validity_end desc', 'validity_start desc', 'jour_semaine', 'debut_matin' ], 'dependents' => 'delete'], 'ouvertures_multimedia' => ['model' => 'Class_Ouverture', 'role' => 'bib', 'scope' => ['multimedia' => 1], 'order' => ['jour', 'validity_end desc', 'validity_start desc', 'jour_semaine', 'debut_matin']], 'exemplaires' => ['model' => 'Class_Exemplaire', 'role' => 'bib', 'dependents' => 'delete'], 'localisations' => ['model' => 'Class_Localisation', 'role' => 'bib', 'order' => 'libelle'], 'plans' => ['model' => 'Class_Plan', 'role' => 'bib', 'order' => 'libelle']], $_belongs_to = ['zone' => ['model' => 'Class_Zone', 'role' => 'bib', 'referenced_in' => 'id_zone'], 'link_to_profil' => ['model' => 'Class_Profil', 'role' => 'bib', 'referenced_in' => 'link_to_profil_id'], 'lieu' => ['model' => 'Class_Lieu', 'role' => 'bib', 'referenced_in' => 'id_lieu']], $_default_attribute_values = ['visibilite' => self::V_INVISIBLE, 'libelle' => '', 'id_zone' => 0, 'ville' => '', 'adresse' => '', 'mail' => '', 'fond' => '', 'telephone' => '', 'aff_zone' => '', 'interdire_resa' => 0, 'google_map' => '', 'photo' => '', 'redmine_login' => '', 'redmine_password' => '', 'redmine_api_key' => '', 'cp' => '', 'lien_carto' => '', 'horaire' => '', 'inscription' => '', 'pret' => '', 'annexe' => '', 'procure' => '', 'closed_on_holidays' => true, 'id_lieu' => 0, 'rewrite_url' => '', 'link_to_profil_id' => 0, 'gln' => ''], $_file_uploaded = false, $_uploadHandler; public function __construct() { parent::__construct(); $this->statut_bib = [$this->_('Invisible'), $this->_('N\'envoie pas de données'), $this->_('Envoie des données')]; } public function describeAssociationsOn($associations) { $associations ->add(new Storm_Model_Association_HasOne('int_bib', ['model' => 'Class_IntBib', 'role' => 'bib', 'referenced_in' => 'id_site'])); } public function numberOfIntMajAutos() { return ($int_bib = $this->getIntBib()) ? $int_bib->numberOfIntMajAutos() : 0; } public function getLieu() { if ($lieu = parent::_get('lieu')) return $lieu; return Class_Lieu::getLoader() ->newInstance() ->setAdresse($this->getAdresse()) ->setCodePostal($this->getCp()) ->setVille($this->getVille()); } public function isPortail() { return $this->getId() === 0; } public function getAffZoneAsArray() { if (!$aff_zone = ZendAfi_Filters_Serialize::unserialize($this->getAffZone())) $aff_zone = []; return array_merge(['profilID' => '0', 'libelle' => $this->getVille(), 'posX' => 0, 'posY' => 0, 'posPoint' => 'gauche'], $aff_zone); } public function getUrl() { if ($this->hasRewriteUrl()) return Class_Url::assemble(['module' => $this->getRewriteUrl()], null, true); $parts = ($profil = $this->_getProfilForUrl()) ? $profil->getUrlParts() : ['controller' => 'bib', 'action' => 'bibview', 'id' => $this->getId()]; return Class_Url::assemble($parts, null, true); } protected function _getProfilForUrl() { return $this->hasLinkToProfil() ? $this->getLinkToProfil() // used on obsolete zone / map configuration : Class_Profil::find($this->getAffZoneAsArray()["profilID"]); } public function isClosedOnHolidays() { return $this->getClosedOnHolidays() == true; } public function getPosX() { return $this->getAffZoneKey('posX'); } public function getPosY() { return $this->getAffZoneKey('posY'); } public function getPosPoint() { return $this->getAffZoneKey('posPoint'); } public function getLibelleAff() { return $this->getAffZoneKey('libelle'); } public function getProfilId() { return $this->getAffZoneKey('profilID'); } public function isSIGBSendData() { return $this->getVisibilite() == static::V_DATA; } /** @return Class_Bib */ public function ensureIntBib() { if (!$int_bib = Class_IntBib::find($this->getId())) $int_bib = Class_IntBib::newInstance(['id_bib' => $this->getId()]); $int_bib->setNomCourt($this->getVille())->save(); return $this; } public function deleteIntBibRelated() { $id_bib = $this->getId(); if (0 < Class_Exemplaire::countBy(['id_bib' => $id_bib])) return $this; Class_IntBib::deleteBy(['id_bib' => $id_bib]); Class_NoticeSuccincte::deleteBy(['id_bib' => $id_bib]); return $this; } public function getAffZoneKey($key) { if (!$aff_zone = ZendAfi_Filters_Serialize::unserialize($this->getAffZone())) return 0; if(!isset($aff_zone[$key])) return 0; return $aff_zone[$key]; } public function getBibsBySite($id_site=0) { $where = ''; if($id_site!=0) $where=" Where ID_SITE=" . (int)$id_site; $bibs=fetchAll("select * from bib_c_site".$where." order by LIBELLE"); if ($bibs==false) $bibs = array(); return $bibs; } // ---------------------------------------------------------------- // Return un select avec la liste des bibs groupées par zone // ---------------------------------------------------------------- public function getComboBib($id_bib=0,$id_zone=0,$all=true, $tag_name="bib") { $selectAll = ''; if (!$id_bib) $selectAll = 'selected="selected"'; $html[]=''; return implode('',$html); } public function getBibById($id_bib) { try { $BibCSite = new BibCSite(); $where = $BibCSite->getAdapter()->quoteInto('ID_SITE=?', $id_bib); return $fetch = $BibCSite->fetchRow($where); }catch (Exception $e) { logErrorMessage('Class: Class_Zone; Function: getBibById' . NL . $e->getMessage()); return $this->_dataBaseError; } } public function getFilePath() { return ($this->getFileName() == $this->getFile()) ? $this->getBasePath().$this->getFile() : $this->getFile(); } public function getFile() { return $this->getPhoto(); } public function getThumbnailPath() { return $this->getBasePath() . self::THUMB_PREFIX . $this->getFile(); } public function getBase($prefix) { return str_replace('//', '/', $prefix.'/'.self::BASE_PATH.'/'); } public function getAllStatus() { return $this->statut_bib; $status= []; foreach ($this->statut_bib as $statut) $status[$statut]=$statut; return $status; } // crée un dictionnaire depuis un liste d'enregistrements. // la clé est la valeur du champ id_field private function rowsToDict(&$rows, $id_field) { $dict = array(); foreach ($rows as $row) { $key = $row[$id_field]; $dict[$key] = $row; } return $dict; } // crée un dictionnaire depuis un liste d'enregistrements. // la clé est la valeur du champ id_field // Chaque clé est associé à un array d'enregistrements correspondant private function groupRowsBy(&$rows, $id_field) { $dict = array(); foreach ($rows as $row) { $key = $row[$id_field]; if (!array_key_exists($key, $dict)) $dict[$key] = array(); $dict[$key] []= $row; } return $dict; } private function treeConfig($type) { // liste des tables, classes, ... pour chaque type de données $configs = array( 'rss' => array( 'cat_class' => 'Class_RssModelCategorie', 'item_class' => 'Class_RssModelFlux', 'cat_table' => 'rss_categorie', 'item_table' => 'rss_flux', 'cat_id_field' => 'ID_CAT', 'item_id_field' => 'ID_RSS', 'cat_parent_id_field' => 'ID_CAT_MERE', 'require' => 'Rss.php'), 'sito' => array( 'cat_class' => 'Class_SitothequeModelCategorie', 'item_class' => 'Class_SitothequeModelSite', 'cat_table' => 'sito_categorie', 'item_table' => 'sito_url', 'cat_id_field' => 'ID_CAT', 'item_id_field' => 'ID_SITO', 'cat_parent_id_field' => 'ID_CAT_MERE', 'require' => 'Sitotheque.php') ); return $configs[$type]; } private function getCategoriesForBibAndTable($id_bib, $cat_table) { $cat_where_clause = $id_bib==0 ? '' : ' WHERE ID_SITE='.$id_bib; $categories = fetchAll('SELECT ID_SITE, ID_CAT,ID_CAT_MERE, LIBELLE '. 'FROM '.$cat_table. $cat_where_clause. ' ORDER BY LIBELLE'); if ($categories==false) $categories = array(); return $categories; } /* Retourne la liste des arborescences bibliothèques / catégories / articles qui ont des articles visibles Utilisé pour construire le JSON envoyer au widget jquery TreeSelect. * type = sito | rss * id_bib : l'id de la bib, 0 signifie toutes les bibliothèques * do_load_items: si true, les items (articles, flux ou sites) ne seront chargés (sinon seulement les catégories). */ public function buildBibTree($id_bib, $type, $do_load_items){ //LL: TODO quand j'aurai le courage, faire une class de base pour les RSS/Sito/CMS et mettre en // place du Strategy pour supprimer toutes les duplications de code //va chercher les tables, classes, noms de champs utilisés $config = $this->treeConfig($type); require_once($config['require']); //en mode portail on doit afficher toutes les bibliothèques $is_portail = ($id_bib==0); $bibs = $this->getBibsBySite($id_bib); if ($is_portail) $bibs []= array('ID_SITE' => 0, 'LIBELLE' => 'Portail'); //on indexe les bib par identifiant pour pouvoir retrouver la bib. de l'item (article, rss, sito) par la suite $bibs = $this->rowsToDict($bibs, 'ID_SITE'); //recherche les catégories puis indexation pour retrouver toutes les catégories d'une bib //(Tout ça pour éviter de faire plusieurs requêtes à la base de données) $categories = $this->getCategoriesForBibAndTable($id_bib, $config['cat_table']); $cat_by_bib = $this->groupRowsBy($categories, 'ID_SITE'); $categories = $this->rowsToDict($categories, $config['cat_id_field']); //Recherche des items par rapport à toutes les catégories trouvées si nécessaire if ((count($categories) > 0) and $do_load_items) { $where_clause = 'WHERE '.$config['cat_id_field'].' IN('.implode(',', array_keys($categories)).')'; //si on est sur le CMS, filtre pour ne prendre que les articles avec date de publication valide $items = fetchAll('SELECT '.$config['cat_id_field'].', '.$config['item_id_field'].', TITRE'. ' FROM '.$config['item_table'].' '. $where_clause. ' ORDER BY '.$config['cat_id_field'].', TITRE'); if ($items==false) $items = array(); $items_by_cat = $this->groupRowsBy($items, $config['cat_id_field']); } //on a les bibs, catégories et items, on construit les arbres pour chaque bib $bib_trees = array(); foreach ($bibs as $bib) { $builder = new CompositeBuilder($config['cat_class'], $config['item_class']); $categories = $cat_by_bib[$bib['ID_SITE']]; if (!$categories && $is_portail) continue; $builder->getRoot()->addCategoriesFromRows($categories, $config['cat_id_field'], $config['cat_parent_id_field']); foreach($categories as $cat) { $items = $items_by_cat[$cat['ID_CAT']]; if ($items) $builder->getRoot()->addItemsFromRows($items, $config['item_id_field'], $config['cat_id_field']); } $bib_cat = new ItemCategory($bib["ID_SITE"]); $bib_cat->setLabel($bib["LIBELLE"]); $bib_cat->addAllCategories($builder->getRoot()->getCategories()); $bib_trees []= $bib_cat; } return $bib_trees; } public function afterSave() { $this->isSIGBSendData() ? $this->ensureIntBib() : $this->deleteIntBibRelated(); } public function getLabel() { return $this->getLibelle(); } public function articlesToJSON($include_items = true) { $json_categories = []; $categories = $this->getArticleCategories(); foreach($categories as $cat) $json_categories []= $cat->toJSON($include_items); return '{'. '"id":'.$this->getId().','. '"label": "'.htmlspecialchars(trim($this->getLibelle())).'",'. '"categories": ['.implode(",", $json_categories).'],'. '"items": []}'; } public function getRecursiveArticleCategories() { $categories=[]; foreach($this->getArticleCategories() as $cat) { $categories[]=$cat; $categories= array_merge($categories,$cat->getRecursiveSousCategories()); } return $categories; } public function getArticleCategoriesToJson() { return $this->articlesToJSON(false); } /** @see Trait_PermissionTargetable */ public function getPermissionsEditUrl($view) { return $view->url(['module' => 'admin', 'controller' => 'bib', 'action' => 'permissions', 'id' => (int)$this->getId()], null, true); } /** @see Trait_PermissionTargetable */ public function getPermissionsChildren() { return $this->getArticleCategories(); } public function getRedmineLoginOrKey() { return ($login = $this->getRedmineLogin()) ? $login : $this->getRedmineApiKey(); } public function isReceivedFile() { if (!$this->_isFileInRequest('photo')) return true; if ($this->_file_uploaded) return $this->_file_uploaded; return $this->_file_uploaded = $this->receiveFile(); } protected function _isFileInRequest($name) { return array_key_exists($name, $_FILES); } public static function setDefaultImageFactory($factory) { static::$_default_image_factory = $factory; } public static function getImageFactory($file) { return isset(static::$_default_image_factory) ? static::$_default_image_factory : new Imagick($file); } public function validate() { $this->checkAttribute('photo', $this->isReceivedFile('photo'), implode(',', array_unique($this->getErrors()))); $validator = new ZendAfi_Validate_RewriteUrl(); $validator->isValid($this); foreach($validator->getMessages() as $message) { $this->checkAttribute('rewrite_url', false, $message); } } public function receiveFile() { $oldFile = $this->getPhoto(); $filename = ($this->getFileName()); $path = ($filename==$oldFile) ? $this->getBasePath().$oldFile : $oldFile; $to_delete = [$path, $this->getBasePath().'thumb_'.$filename]; $on_update = function($old_file, $new_file) use ($to_delete) { if ($old_file && $old_file != $new_file) foreach($to_delete as $item) $this->getFileSystem()->unlink($item); $this->createThumbnail(); return true; }; $handler = $this->getUploadHandler('photo', static::UPLOAD_MAX_SIZE) ->setAllowedExtensions(['jpg', 'gif', 'png', 'jpeg']); return (new Class_FileProperty($this, 'photo', $handler, $on_update)) ->receive(); } public function createThumbnail() { if ('' == $this->getFile()) return true; try { $image = self::getImageFactory($this->getBasePath() . $this->getFile()); $image->thumbnailImage(160, 0); if (!$image->writeImage($this->getThumbnailPath())) { $this->addError('Erreur lors de l\'enregistrement de la vignette'); return false; } return true; } catch (Exception $e) { $this->addError('Erreur lors de la création de la vignette ' . (string)$e->getMessage()); return false; } } public function getFileName() { $path=explode('/',$this->getFile()); return end($path); } public function getBasePath() { return $this->getBase(USERFILESPATH); } public function getUploadHandler($name,$limit_size_in_ko=-1) { if (null === $this->_uploadHandler) { $this->_uploadHandler = Class_Upload::newInstanceFor($name) ->setBaseName($this->getId()) ->setBasePath($this->getBasePath()) ->setMaxSize($limit_size_in_ko); } return $this->_uploadHandler; } public function setUploadMover($name, $mover) { $this->getUploadHandler($name,static::UPLOAD_MAX_SIZE)->setUploadMover($mover); return $this; } public function getFacet() { return Class_Bib::CODE_FACETTE . $this->getIdSite(); } public function getFacetLabel() { return $this->getIntBib() ? $this->getIntBib()->getNomCourt() : $this->getLibelle(); } public function includeItem($item) { return $this->getId() == $item->getIdBib(); } public function getOuvertureOnDate($time) { if($this->hasHoraire()) return null; if ($ouverture = Class_Ouverture::findFirstBy(['jour' => date('Y-m-d', $time), 'id_site' => $this->getId()])) return $ouverture; if ($this->isClosedOnHolidays() && Class_Date_Holiday::isHoliday($time)) return null; $day_of_week = Class_Date::dayOfWeek($time); $openings = new Storm_Model_Collection($this->getOuvertures()); $blessed = $openings->select('hasValidityRange') ->detect( function($o) use ($time, $day_of_week) { return $o->isValidOnDate($time) && ($o->getJourSemaine() == $day_of_week); }); return $blessed ? $blessed : $openings->reject('hasValidityRange') ->detect( function($o) use ($day_of_week) { return $o->getJourSemaine() == $day_of_week; }); } public function isOpened() { return null !== $this->getOuvertureOnDate($this->getCurrentTime()); } public function isOpenedNow() { if (!$ouverture = $this->getOuvertureOnDate($this->getCurrentTime())) return false; return Class_Ouverture_State::from($ouverture) ->onIsOpenedUntil(true) ->evaluateAt($this->getCurrentTime()); } public function getZoneLibelle() { return ($zone = $this->getZone()) ? $zone->getLibelle() : ''; } public function acceptOpeningsVisitor($visitor) { $visitor->visitSimple($this->getHoraire()); foreach($this->getOuvertures() as $opening) $visitor->visitOpening($opening); return $this; } public function getLatitude() { return $this->hasLieu() ? $this->getLieu()->getLatitude() : null; } public function getLongitude() { return $this->hasLieu() ? $this->getLieu()->getLongitude() : null; } }