Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"php": ">=7.4.0",
"yiisoft/yii2": "~2.0.45",
"yiisoft/yii2-bootstrap5": "~2.0.2",
"yiisoft/yii2-symfonymailer": "~2.0.3"
"yiisoft/yii2-symfonymailer": "~2.0.3",
"yiisoft/yii2-httpclient": "^2.0"
},
"require-dev": {
"yiisoft/yii2-debug": "~2.1.0",
Expand Down
28 changes: 26 additions & 2 deletions controllers/SiteController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

namespace app\controllers;

use Yii;
use yii\web\Controller;
use app\models\Post;
use yii\data\Pagination;

class SiteController extends Controller{
/**
Expand Down Expand Up @@ -31,6 +34,27 @@ public function actionIndex(){
* @return string
*/
public function actionTest1(){
return $this->render('test1');

// Number of posts per page
$pageSize = 9;

// Get current page number from the request
$page = Yii::$app->request->get('page', 1);

// Fetch posts from the Post model including pagination
$posts = Post::fetchAll($page, $pageSize);

// Set up pagination
$pagination = new Pagination([
'totalCount' => $posts['totalCount'],
'pageSize' => $pageSize,
'page' => $page - 1, // Adjust for zero-based index
]);

// Render the view with posts and pagination
return $this->render('test1', [
'posts' => $posts['data'],
'pagination' => $pagination,
]);
}
}
}
3 changes: 1 addition & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
version: '2'
services:
php:
image: yiisoftware/yii2-php:7.4-apache
volumes:
- ~/.composer-docker/cache:/root/.composer/cache:delegated
- ./:/app:delegated
ports:
- '8000:80'
- '8080:80'
90 changes: 90 additions & 0 deletions models/Post.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace app\models;

use yii\base\Model;

// Include Yii2 HTTP Client
use yii\httpclient\Client;

class Post extends Model
{
/*
Fetch all posts from the external JSONPlaceholder API with pagination support.
@param int $page The page number to fetch.
@param int $limit The number of posts per page.
@return array containing 'data' (posts) and 'totalCount' (total number of posts).
*/
public static function fetchAll($page = 1, $limit = 9)
{
// Create Yii2 HTTP Client
$client = new Client(['baseUrl' => 'https://jsonplaceholder.typicode.com/']);

// Try to send the request and handle possible errors
try {

// Send GET request to the /posts endpoint using Yii2 HttpClient with params for pagination
// Documentation: https://github.com/typicode/json-server#paginate
// We use _limit instead of _per_page because JSONPlaceholder is using old json-server version
$response = $client->get('posts', [
'_page' => $page,
'_limit' => $limit,
])->send();

// Check if the response is successful
if ($response->isOk) {
$posts = $response->data;

// Total count of posts from the headers
$totalCount = (int) $response->headers->get('X-Total-Count', count($posts));

// Return posts data and total count
return [
'data' => $posts,
'totalCount' => $totalCount,
];
}

/*
Handle different error status codes from the API related with getting posts.
Taking into account it's not necessary credentials for this API.
*/
switch ($response->statusCode) {
case 400:
$message = 'Bad Request (400).';
break;
case 404:
$message = 'Resource not found (404).';
break;
case 500:
$message = 'Internal server error (500).';
break;
case 503:
$message = 'Service unavailable (503). Please try again later.';
break;
default:
$message = 'Unknown error. Code: ' . $response->statusCode;
break;
}

// Yii2 log to capture possible API errors
Yii::warning("Error API ({$response->statusCode}): {$message}", __METHOD__);
Comment on lines +70 to +71
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Yii class is being used for logging but hasn't been imported. Please add use Yii; at the top of the file with the other imports.


return [
'error' => true,
'message' => $message,
];


} catch (\Exception $e) {

// Yii2 log to capture connection errors
Yii::error("Failed to connect: " . $e->getMessage(), __METHOD__);
return [
'error' => true,
'message' => 'Sorry, there was an error connecting to the external {JSON} Placeholder API.',
];
}

}
}
2 changes: 1 addition & 1 deletion views/layouts/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
</div>
</main>

<footer id="footer" class="mt-auto py-3 bg-light">
<footer id="footer" class="mt-auto py-3 bg-dark">
<div class="container">
<div class="row text-muted">
<div class="col-md-12 text-center">&copy; <?=Yii::$app->name?> <?=date('Y')?></div>
Expand Down
46 changes: 43 additions & 3 deletions views/site/test1.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

/** @var yii\web\View $this */

/* Yii2 pagination widget */
use yii\widgets\LinkPager;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a namespace inconsistency with the LinkPager widget. At the top of the file, you import yii\widgets\LinkPager, but later use \yii\bootstrap5\LinkPager. You should update the import statement to match the actual implementation.


$this->title = 'Test 1 — ooptimo';
?>
<div class="site-test1">
Expand All @@ -25,9 +28,46 @@

<hr>

<div class="posts-list">
<div class="posts-list mb-5">
<div class="row mt-4">
<h2>Posts</h2>
<h2 class="mb-4">Posts</h2>
</div>

<div class="row">
<!-- Check if there was an error fetching posts -->
<?php if ( isset($posts['error']) ): ?>
<div class="col-md-12">
<div class="alert alert-danger" role="alert">
<!-- Display the error message -->
<?php echo $posts['message']; ?>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message is being output directly without HTML escaping, which could lead to XSS vulnerabilities if the error message contains HTML tags or scripts.

Suggested change
<?php echo $posts['message']; ?>
<!-- Display the error message -->
<?php echo htmlspecialchars($posts['message']); ?>

</div>
</div>
<?php else: ?>
<!-- Loop and display each post -->
<?php foreach ( $posts as $post ): ?>
<div class="col-md-4 mb-4">
<div class="card h-100">
<div class="card-body">
<!-- Escape output to prevent HTML injection -->
<h5 class="card-title"><?= htmlspecialchars($post['title']) ?></h5>
<p class="card-text"><?= htmlspecialchars($post['body']) ?></p>
</div>
<div class="card-footer text-ooptimo">
<small>By <strong>User <?= htmlspecialchars($post['userId']) ?></strong> · Post <strong>#<?= htmlspecialchars($post['id']) ?></strong></small>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>

<div class="row">
<div class="col-md-12 align-center">
<div class="pagination-wrapper align-center mt-4">
<!-- Yii2 pagination widget, with Bootstrap 5 structure -->
<?= \yii\bootstrap5\LinkPager::widget(['pagination' => $pagination]) ?>
</div>
</div>
</div>
</div>
</div>
</div>
43 changes: 43 additions & 0 deletions web/css/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,46 @@ main > .container {
margin-top: 5px;
color: #999;
}


/* Custom CSS Variables and Styles for OOPTIMO by Carlos Turmo */
:root{
--primaryColor: #223061;
--secondaryColor: #9eade6;
--greenColor: #88d3ba;
}

.text-ooptimo{
color: var(--primaryColor)!important;
}

.bg-dark{
background-color: var(--primaryColor)!important;
}

.navbar-dark{
--bs-navbar-brand-color: var(--primaryColor);
background-color: var(--secondaryColor)!important;
}


.pagination{
--bs-pagination-color: var(--primaryColor);

--bs-pagination-active-color: var(--primaryColor);
--bs-pagination-active-bg: var(--greenColor);
--bs-pagination-active-border-color: var(--greenColor);

--bs-pagination-hover-color: var(--primaryColor);
--bs-pagination-hover-bg: var(--secondaryColor);
--bs-pagination-hover-border-color: var(--secondaryColor);

--bs-pagination-focus-color: var(--primaryColor);
--bs-pagination-focus-bg: var(--secondaryColor);
}

#footer{
.text-muted{
color: var(--bs-white)!important;
}
}