All about the development of eZ Publish by Graham Brookins, 7x (formerly Brookins Consulting) and our think tank kracker.org.
I bring news of a new solution 7x is working on for developers with an interest to syndicate information without an rss / atom / xml / etc feed available.
Today if you want to include your companies YouTube Playlist videos (say commercials or educational materials) you can embed this content manually within the administrator content editing features available by default.
What if you want to sync content from YouTube like your companies playlist video content (the meta data not the actual videos, playback is through YouTube's embed video URL APIs) into the eZ Content Tree Automatically?
Currently this is possible today without a feed via old fashioned web page scraping of specific account playlists video title and video URL URI v element data (video's id) using RSS-Bridge.
Normally RSS-Bridge (doc) is a standalone application well written in OOP PHP. Because documentation on using the library standalone is limited we created a solution to bootup the software layers and call the provided YouTube Playlist Data Collection APIs from RSS-Bridge within an eZ Publish Request Scope within a eZ Publish Cronjob Part Script.
Filename: extension/sevenx_rss_bridge/cronjobs/7xRssBridgeYoutubeFeedImport.php
<?php if ( !$isQuiet ) { $cli->output( "Creating youtube video object(s) ..." ); } $count = SevenxRssBridgeFeedImport::importFeedContentObjects(); if ( !$isQuiet ) { $cli->output( "Number of objects created: $count" ); $cli->output( "Done." ); } ?>
Got your attention now with a stub for calling the features we need within eZ (Remember to enable via cronjob settings and clear all caches).
Filename: extension/sevenx_rss_bridge/settings/cronjobs.ini.append.php
<?php /* #?ini charset="utf8"? [CronjobSettings] ExtensionDirectories[]=sevenx_rss_bridge [CronjobPart-youtube-feed-import] Scripts[]=7xRssBridgeYoutubeFeedImport.php */ ?>
Next we have the worker class that does the work to fetch the YouTube information, create the YouTube videos (Meta Data Based Embeds in a Playlist) underneath Youtube Playlist Nodes Containing the Playlist Meta Data.
With this we first create the playlist nodes (locations) with the required playlist YouTube embed URL + Parameters (like list which helps provide long form content within a playlist). With the following code in place (remember to regenerate eZ's Autoloads) we can call the cronjob and sync / import the data from YouTube into eZ Publish Content Tree.
Filename: extension/sevenx_rss_bridge/classes/SevenxRssBridgeFeedImport.php
<?php class SevenxRssBridgeFeedImport { public static function importFeedContentObjects() { // Settings $storageNodeID = eZINI::instance('sevenx_videos.ini')->variable('PlaylistSettings','PlaylistNodeID'); // Fetch Playlists Objects to store the videos underneath. $playlists = self::fetchNodeContent($storageNodeID ); // Initialize the count of imported items $importedCount = 0; // Include RSS-Bridge autoloader require_once('vendor/rss-bridge/rss-bridge/lib/bootstrap.php'); foreach ( $playlists as $playlist ) { if ( $playlist->attribute('children_count') <= 0 ) { $playlistsNodeID = $playlist->attribute('node_id'); $dm = $playlist->dataMap(); $playlistID = $dm[ 'youtube_url' ]->content(); if ( !str_contains($playlistID, 'videoseries' ) ) continue; $playlistID = explode( 'list=', $playlistID )[1]; $bridgeParams = [ 'p' => $playlistID ]; try { $main = new RssBridge(); $bridgeFactory = new BridgeFactory(); // Directly instantiate the YouTubeBridge class $bridge = $bridgeFactory->create( YoutubeBridge::class ); // Set parameters $bridge->setInput( $bridgeParams ); // Fetch data $data = $bridge->collectData(); $items = $bridge->getItems(); //var_dump( $items ); // Process each video entry foreach ($items as $item) { $importedCount += self::importYouTubeVideo($item, $playlistsNodeID, $playlistID); } } catch (Exception $e) { echo "Error: " . $e->getMessage() . "\n"; return 0; // Return 0 in case of error } } } echo "Successfully imported YouTube videos.\n"; return $importedCount; // Return the count of imported items } private static function importYouTubeVideo($item, $storageNodeID, $playlistID) { $adminUser = 'admin'; // no password used so safe and available by default. change if needed. $classID = 48; // Note: Please change to your video class. // Extract video data $title = $item['title']; $description = $item['content']; $url = $item['uri']; $video = explode( '?v=', $url )[1]; $url = "https://www.youtube.com/embed/$video?si=" . $video . '&list=' . $playlistID; // var_dump($item['title']); // var_dump($url); // die('fin'); // return 1; // Fetch admin user $user = eZUser::fetchByName( $adminUser ); $userCreatorID = $user->attribute( 'contentobject_id' ); // Create object $defaultSectionID = 1; $class = eZContentClass::fetch( $classID ); $contentObject = $class->instantiate( $userCreatorID, $defaultSectionID ); // Set remote_id content //$remoteID = "contentserver:incomingnode"; //$contentObject->setAttribute( 'remote_id', $remoteID ); $contentObject->store(); // Fetch related IDs $contentObjectID = $contentObject->attribute( 'id' ); //$userID = $contentObjectID; // Create node assignment $nodeAssignment = eZNodeAssignment::create( array( 'contentobject_id' => $contentObjectID, 'contentobject_version' => 1, 'parent_node' => $storageNodeID, 'is_main' => 1 ) ); $nodeAssignment->store(); // Set version modified and status content $version = $contentObject->version( 1 ); $version->setAttribute( 'modified', time() ); $version->setAttribute( 'status', eZContentObject::STATUS_DRAFT ); $version->store(); // Fetch contentObject IDs $contentObjectID = $contentObject->attribute( 'id' ); $contentObjectAttributes = $version->contentObjectAttributes(); // Set Name $contentObjectAttributes[0]->setAttribute( 'data_text', $title ); $contentObjectAttributes[0]->store(); // Set Name $contentObjectAttributes[2]->fromString( "$url|$title" ); $contentObjectAttributes[2]->store(); // Publish content object to top level root node $operationResult = eZOperationHandler::execute( 'content', 'publish', array( 'object_id' => $contentObjectID,'version' => 1 ) ); echo "Imported video: $title\n $url\n"; return 1; // Return 1 to indicate successful import } public static function fetchNodeContent( $nodeID = 2, $limit = false, $classIdentifierArray = array( 'playlist' ) ) { $node = eZContentObjectTreeNode::fetch($nodeID); if (!$node) { echo "Node with ID $nodeID not found.\n"; return []; } // Fetch only objects of 'playlist' class $params = [ 'ClassFilterType' => array( 'include' ), 'ClassFilterArray' => $classIdentifierArray, // Replace 'playlist' with your desired class identifier 'Limit' => $limit // Adjust the limit as needed ]; $result = eZContentObjectTreeNode::subTreeByNodeID($params, $nodeID); return $result; } } ?>
Here is an example of calling the cronjob script. We do this after the above is properly installed and configured.
cd /path/to/ezpublish/; clear; date;./runcronjobs.php youtube-feed-import; date
This solution works well by simulation of the RSS-Bridge Application which effortlessly (once you learn how to call it correctly) fetches Youtube / Instagram / Telegram / Other content for you to store and use within your own website powered by eZ Publish.
We are working to release a package of this software ready to install and customize to meet your own needs. Until then use this blog post as a project origin story / working example of a solution for content stored in web applications which do not provide feeds.
I write today to reflect on the nature and type of work being done recently at 7x.
I fined that I have learned a new skill and that is default installation (dynamic setting and database configuration) programming via eZ Publish package development in the form of what is called a site package.
7x has developed a new replacement for the older ezflow, ezdemo, ezwebin site package installers called sevenx (technically sevenx_site.ezpkg).
This solution was made part of the release of eZ Publish 6 on or before January 1st 2024.
While traditional kernel development (file changes in the ezpublish repository) have slowed in February after the release of eZ Publish 6.0.1 on February 1st 2024 another type of development has resumed which is eZ Publish default installation testing and development which has included the upkeep and amount of the packages.ezpublishlegacy.se7enx.com package server.
I wrote previously in support of an increased presence of an eZ Publish Package Server in the talking points surrounding a feature complete cms.
I also wrote previously in support of Netgen supporting eZ Publish with implementing a feature complete update of the package server needed to distribute eZ Publish to new users and in general which 7x took up the challenge to do the work first to share with Netgen.
While other vendors focus on just a small feature addition here or there within an eZ Publish website; 7x is looking at the entire system as a platform which means we see needs that often go overlooked like self-hosting the eZ Publish Package Server and maintaining the packages it hosts to the general public.
We moved this older se7enx.com resource from our own hosting to a faster more reliable GitHub based hosting. This ensures future collaboration with others with eZ Publish Packages for distribution.
We recently shared a older but solid ezpaypal-ezpackage repository reverse engineered from binary ezpkg sources licensed under the GNU GPL. We updated this repository to provide the latest eZ PayPal version 1.2.0 software (a now long stable software package).
This package is installed as a required dependency of the 7x eZ package (sevenx_site) and soon will be installed as a dependency of the other site packages (ezwebin, ezdemo, ezflow) once we get closer to 6.0.2 and 6.0.3. As it takes time to add the required code and release it to the git repositories and package server per package (dependencies required; collection).
This will mean that the older site installers (each site package contains an installer php file that does the work) for each site package (ezwebin, ezdemo, ezflow) (content and no-content) will get a refresh and soon be worth testing all over again.
We will come to this point once we begin to merge over changes made to sevenx_site package to the other packages like the installer, package xml, and packaged dependencies get updated when we begin to refresh the actual content installed (content tree). We are aiming to have this completed for the 6.0.3 release.
This will strip out eZ Systems Products and Marketing from the default demo website and allow us to finally refresh the content class datatypes used (add html5 video support to the existing video class among many others like installing more example data like enable comments by default and have them working; Also Add ezstarrating datatype to most of all the content classes; experiment adding xrowmetadata extension into the default data as it requires very little configuration to start using, ++). Lots of ideas of what more features we could offer by default to default installation users who could be swayed by a new feature rich demo of what we have already we just need to start offering it by default in the default demo content as installed by the site package selection (which installs a demo content package of classes and objects (content tree nodes)).
It's deep stuff and it is time consuming to test to ensure the build of available packages remains stable and functional as you never know who is testing the current packages selection at any given time.
Yet it is rewarding once the package server contents has become much more stable and feature rich as it has been refactored lightly with the lessons of years of eZ Publish development (simplifying the var dir pathing in a default installation for example).
If your interested in sharing your extensions on our 7x eZ Publish Package Server; Please reach out and contact me via email.
Add comment