0

I am upgrading a client's CLi script that is used to automatically bulk import news items into Joomla articles. The code was originally programmed for Joomla 3 and now needs to support Joomla 4.

I have completed quite a few upgrades, but am now stuck looping through the news item data and storing it using the Content::getInstance('Article', 'ContentModel') method. I have included my code below for reference.

Updated September 18

Current use statements

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\Component\Content\Administrator\Model\ArticleModel;
use Joomla\CMS\Table\Content;
use Joomla\CMS\Filter\OutputFilter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Joomla\Console\Command\AbstractCommand;

Use of $app = Factory::getApplication();

Current insertArticles function

private function insertArticles(array $articles): array {

  $articleModel = new ArticleModel();

  foreach ($this->removeDuplicateAliases($articles) as $article) {
    $articleData = [
      'id' => 0,
      'catid' => 13,
      'title' => ucwords(strtolower($article->title)),
      'alias' => OutputFilter::stringURLSafe($article->title),
      'introtext' => strip_tags($article->storySummary, ['p', 'img', 'table']),
      'fulltext' => strip_tags($article->content, ['p', 'img', 'table']),
      'note' => 'DTN supplied topics: ' . str_replace('/', ', ', $article->topic),
      'state' => 1,
      'access' => 1,
      'created_by' => 386,
      'created_by_alias' => 'DTN News',
      'language' => '*',
      'metadata' => [
        'xreference' => $article->dtnpfUrl,
      ],
      'attribs' => [
        'show_publish_date' => 1,
      ],
      'rules' => [
        'core.edit.delete' => [],
        'core.edit.edit' => [],
        'core.edit.state' => []
      ],
    ];

    if (!$articleModel->save($articleData)) {
      throw new Exception($articleModel->getError());
    } else {
      $newIds[] = $articleModel->getItem()->id;
    }
  }
  return $newIds ?? [];
}

Updated

Included use statements from the command file for reference:

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\ItemModel;
use Joomla\Component\Content\Site\Model;
use Joomla\CMS\Table\Content;
use Joomla\CMS\Filter\OutputFilter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Joomla\Console\Command\AbstractCommand;

Command line error

Class "Ploszmarketing\Plugin\Console\Dtnnews\CliCommand\ArticleModel" not found

protected function doExecute(InputInterface $input, OutputInterface $output): int {
  $symfonyStyle = new SymfonyStyle($input, $output);

  $symfonyStyle->title('DTN News Import');

  // Fetch the JSON data and Run the insertArticle bulk data import to articles function
  $curlOpts = [
    CURLOPT_URL => 'https://api.dtn.com/publishing/news/articles?categoryId=1%2C2%2C3%2C4%2C5%2C6%2C16%2C17&limit=10&maxAge=15&apikey=placeholder',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => '',
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 0,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => 'GET',
  ];

  try {
    $fetchedArticles = $this->curlToObject($curlOpts);
  
    if ($fetchedArticles) {
      $symfonyStyle->title(Text::_('Fetched Articles: ' . count($fetchedArticles)));
    }
  
    // Errors occur here
    $insertedIds = $this->insertArticles($fetchedArticles);
  
    if ($insertedIds) {
      $symfonyStyle->title(Text::_('Inserted IDs: ' . implode(', ', $insertedIds)));
    }
  
  } catch (exception $e) {
    $symfonyStyle->error(Text::_($e->getMessage()));
  }

  $symfonyStyle->success('Successful import!');

  return 0;

}

private function curlToObject(array $curlOpts): array {
  $ch = curl_init();
  curl_setopt_array($ch, $curlOpts);
  $result = curl_exec($ch);
  $error = curl_error($ch); // Get the last error
  curl_close($ch);
  if ($error) {
    throw new Exception($error);
  }
  return json_decode($result);
}

private function insertArticles(array $articles): array {
  // Replaced articleModel method with recommendation
  // $articleModel = Content::getInstance('Article', 'ContentModel');
  $articleModel = new ArticleModel();

  foreach ($this->removeDuplicateAliases($articles) as $article) {
    $articleData = [
      'id' => 0,
      'catid' => 13,
      'title' => ucwords(strtolower($article->title)),
      'alias' => OutputFilter::stringURLSafe($article->title),
      // I know strip_tags is deprecated for 8.2. I will fix after issues are resolved.
      'introtext' => strip_tags($article->storySummary, ['p', 'img', 'table']),
      'fulltext' => strip_tags($article->content, ['p', 'img', 'table']),
      'note' => 'DTN supplied topics: ' . str_replace('/', ', ', $article->topic),
      'state' => 1,
      'access' => 1,
      'created_by' => 386,
      'created_by_alias' => 'DTN News',
      'language' => '*',
      'metadata' => [
        'xreference' => $article->dtnpfUrl,
      ],
      'attribs' => [
        'show_publish_date' => 1,
      ],
      'rules' => [
        'core.edit.delete' => [],
        'core.edit.edit' => [],
        'core.edit.state' => []
      ],
    ];

    if (!$articleModel->save($articleData)) {
      throw new Exception($articleModel->getError());
    } else {
      $newIds[] = $articleModel->getItem()->id;
    }
  }
  return $newIds ?? [];
}

private function removeDuplicateAliases(array $articles): array {
  $db = JFactory::getDbo();
  $query = $db->getQuery(true);
  $query
    ->select('a.alias')
    ->from($db->quoteName('#__content', 'a'))
    ->where($db->quoteName('a.catid') . " = " . $db->quote('13'));
  $db->setQuery($query);
  $aliasLookup = array_flip($db->loadColumn());

  foreach ($articles as $i => $article) {
    if (isset($aliasLookup[OutputFilter::stringURLSafe($article->title)])) {
      unset($articles[$i]);
    }
  }
  return $articles;
}

Error in the command line: Call to a member function save() on bool

Exception trace:

 at /var/www/upgrade/plugins/console/dtnnews_cli/src/CliCommand/RunDtnnewsCommand.php:126
 Ploszmarketing\Plugin\Console\Dtnnews\CliCommand\RunDtnnewsCommand->insertArticles() at /var/www/upgrade/plugins/console/dtnnews_cli/src/CliCommand/RunDtnnewsCommand.php:68
 Ploszmarketing\Plugin\Console\Dtnnews\CliCommand\RunDtnnewsCommand->doExecute() at /var/www/upgrade/libraries/vendor/joomla/console/src/Command/AbstractCommand.php:235
 Joomla\Console\Command\AbstractCommand->execute() at /var/www/upgrade/libraries/vendor/joomla/console/src/Application.php:1181
 Joomla\Console\Application->runCommand() at /var/www/upgrade/libraries/vendor/joomla/console/src/Application.php:403
 Joomla\Console\Application->doExecute() at /var/www/upgrade/libraries/src/Application/ConsoleApplication.php:206
 Joomla\CMS\Application\ConsoleApplication->doExecute() at /var/www/upgrade/libraries/vendor/joomla/console/src/Application.php:448
 Joomla\Console\Application->execute() at /var/www/upgrade/libraries/src/Application/ConsoleApplication.php:259
 Joomla\CMS\Application\ConsoleApplication->execute() at /var/www/upgrade/cli/joomla.php:76

I am unsure of how to handle the for loop with the new model in Joomla 4.

The Joomla 3 insertArticles function is below

private function insertArticles(array $articles): array {
  require_once JPATH_ADMINISTRATOR . '/components/com_content/models/article.php';
  $articleModel = new ContentModelArticle([]);

  foreach ($this->removeDuplicateAliases($articles) as $article) {
    $articleData = [
      'id' => 0,
      'catid' => 13,
      'title' => ucwords(strtolower($article->title)),
      'alias' => JFilterOutput::stringURLSafe($article->title),
      'introtext' => strip_tags($article->storySummary, ['p', 'img', 'table']),
      'fulltext' => strip_tags($article->content, ['p', 'img', 'table']),
      'note' => 'DTN supplied topics: ' . str_replace('/', ', ', $article->topic),
      'state' => 1,
      'access' => 1,
      'created_by' => 386,
      'created_by_alias' => 'DTN News',
      'language' => '*',
      'metadata' => [
        'xreference' => $article->dtnpfUrl,
      ],
      'attribs' => [
        'show_publish_date' => 1,
      ],
      'rules' => [
        'core.edit.delete' => [],
        'core.edit.edit' => [],
        'core.edit.state' => []
      ],
    ];

    if (!$articleModel->save($articleData)) {
      throw new Exception($articleModel->getError());
    } else {
      $newIds[] = $articleModel->getItem()->id;
    }
  }
  return $newIds ?? [];
}

Any assistance is greatly appreciated.

1 Answer 1

0

The error message is saying that you are trying to call save() from a boolean value, either true or false. The reason $articleModel is false is due to it not being created as you expected, returning a value of false instead of the Model object.

The reason it failed is because you have used the Joomla 3 class name for the model and not the Joomla 4 class name.

This line in your insertArticles()

  $articleModel = new ContentModelArticle([]);

needs to be changed to

  $articleModel = new ArticleModel();

I have removed the array '[]'argument as I don't see that it is required in either version.

In the ..\components\com_content\src\Model\ArticleModel.php that you are referring to in the require_once line above the calling of the ArticleModel class you can see the name you need to be using.

/**
 * Content Component Article Model
 *
 * @since  1.5
 */
class ArticleModel extends ItemModel
{
    /**

Update: 17th September

There are several way you can achieve this within Joomla 4

$articleModel = new \Joomla\Component\Content\Administrator\Model\ArticleModel();

With a use statement that removes the need for the require_once statement.

use Joomla\Component\Content\Administrator\Model\ArticleModel;
...
$articleModel = new ArticleModel();

Or by getting the model from the dependency injection container

$mvcFactory = $this->app->bootComponent('com_content')->getMVCFactory();
$articleModel = $mvcFactory->createModel('Article', 'Administrator', ['ignore_request' => true]);

You can see an example of the last method in ..\plugins\sampledata\blog\blog.php at line #679 onwards where it is used in code very similar to what you are doing by adding sample articles to the Joomla instance.

$mvcFactory = $this->app->bootComponent('com_content')->getMVCFactory();

// Store the articles
foreach ($articles as $i => $article) {
            $articleModel = $mvcFactory->createModel('Article', 'Administrator', ['ignore_request' => true]);
...

    if (!$articleModel->save($article)) {
...
}
6
  • I have swapped out the code on the line and have updated my question with additional code. The CLi now shows an error Class "Ploszmarketing\Plugin\Console\Dtnnews\CliCommand\ArticleModel" not found. I used the namespace from content component article model. Sep 16 at 13:03
  • Without seeing the code you have added it is hard to say what is going on now but from the message it looks like it looking for ArticleModel in the wrong namespace. I will update my answer with some more details.
    – Irata
    Sep 16 at 23:49
  • Thanks for your assistance. I went with the use statement and am encountering this error: Joomla\CMS\Workflow\Workflow::__construct(): Argument #2 ($app) must be of type ?Joomla\CMS\Application\CMSApplication, Joomla\CMS\Application\Con soleApplication given, called in /var/www/upgrade/libraries/src/MVC/Model/WorkflowBehaviorTrait.php on line 90. I have updated my question with additional code. Sep 18 at 17:50
  • You now have a different issue that should probably be created as a new question on JSE however the short answer I think is to disable the Work Flow in Article:Options under the Integration's tab of the back end.
    – Irata
    Sep 18 at 22:54
  • The Work Flow option was already disabled in Article > Options. I will test some more before opening another question. Thanks for your assistance. Sep 19 at 17:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.