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": "*"
},
"require-dev": {
"yiisoft/yii2-debug": "~2.1.0",
Expand Down
68 changes: 67 additions & 1 deletion controllers/SiteController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

use yii\web\Controller;

use yii\httpclient\Client;

use yii\data\Pagination;

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

/**
* For a simple retrieve case like this, we could use file_get_contents to fetch the data
* but since this is a Yii2 test, let's use Yii2's HTTP Client component to demonstrate best practices and
* proper usage of the framework, while avoiding possible allow_url_fopen exploits errors.
*
* Also let's try Yii2's pagination just to learn how it works, even if it's not required in the task.
*/

$client = new Client();
$response = $client->createRequest()
->setMethod('GET')
->setUrl("https://jsonplaceholder.typicode.com/posts")
->send();
if ($response->isOk) {
$data = $response->data;
} else {
$data = [];
}

$count = count($data);
$pagination = new Pagination(['totalCount' => $count, 'pageSize' => 6]);
$articles = array_slice($data, $pagination->offset, $pagination->limit);

return $this->render('test1', [
'data' => $articles,
'pagination' => $pagination
]);
}

/**
* Displays single post.
*
* @return string
*/
public function actionPost($postId = null, $page = 0){

/**
* Since we have some time left, let's add a simple post view page with a simple "post not found" handling.
* (you can test it by changing the postId parameter to a non-existing one in the URL)
*/

if(empty($postId)) {
return $this->redirect(['site/test1']);
} else {
$client = new Client();
$response = $client->createRequest()
->setMethod('GET')
->setUrl("https://jsonplaceholder.typicode.com/posts/{$postId}")

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 potential security issue with directly inserting the $postId parameter into the URL string. While jsonplaceholder.typicode.com might handle this safely, it's a good practice to validate or sanitize URL parameters before using them in requests.

Suggested change
->setUrl("https://jsonplaceholder.typicode.com/posts/{$postId}")
->setUrl("https://jsonplaceholder.typicode.com/posts/" . intval($postId))

->send();

if ($response->isOk) {
$post = $response->data;

} else {
$post = [];
}
Comment on lines +89 to +94

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 handling in actionPost() could be improved. When the API request fails, you set $post to an empty array, but then render the view without checking if the post data is valid. This might cause errors in the view if it tries to access properties of a non-existent post.

Consider adding a check before rendering or redirecting to an error page:

Suggested change
if ($response->isOk) {
$post = $response->data;
} else {
$post = [];
}
if ($response->isOk) {
$post = $response->data;
if (empty($post)) {
return $this->redirect(['site/test1']);
}
} else {
return $this->redirect(['site/test1']);
}

}

return $this->render('post', [
'post' => $post,
'page' => $page
]);
}
}
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions views/site/post.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

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

use yii\helpers\Url;

$this->title = 'Post — ooptimo';
?>
<div class="site-post">
<div class="body-content">
<a href="<?=Url::to(['site/test1', 'page' => $page])?>" class="btn btn-primary btn-sm mb-3">Back to posts list</a>
<div class="row mt-3">
<?php if ($post): ?>
<img src="https://placehold.co/800x400" alt="Placeholder image" class="img-fluid mb-4">
<h2><?= htmlspecialchars($post['title']) ?></h2>
<p><?= htmlspecialchars($post['body']) ?></p>
<?php else: ?>
<p>Post not found.</p>
<?php endif; ?>
</div>
</div>
</div>
22 changes: 22 additions & 0 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 */

use yii\helpers\Url;
use yii\widgets\LinkPager;

$this->title = 'Test 1 — ooptimo';
?>
<div class="site-test1">
Expand Down Expand Up @@ -29,5 +32,24 @@
<div class="row mt-4">
<h2>Posts</h2>
</div>
<div class="row mt-3">
<?php if(empty($data)): ?>
<p>No posts found.</p>
<?php else: ?>
<?php foreach($data as $post): ?>
<div class="col-md-4 post-item mb-3">
<a href="<?=Url::to(['site/post', 'postId' => $post['id'], 'page' => isset($_GET['page']) ? $_GET['page'] : 1])?>"><img src="https://placehold.co/600x400" alt="Placeholder image" class="img-fluid mb-2"></a>
<a href="<?=Url::to(['site/post', 'postId' => $post['id'], 'page' => isset($_GET['page']) ? $_GET['page'] : 1])?>" class="post-title"><h4><?= htmlspecialchars($post['title']) ?></h4></a>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Instead of directly accessing $_GET['page'], consider using Yii's request component: Yii::$app->request->get('page', 1). This is safer and follows Yii2 best practices.

Suggested change
<a href="<?=Url::to(['site/post', 'postId' => $post['id'], 'page' => isset($_GET['page']) ? $_GET['page'] : 1])?>" class="post-title"><h4><?= htmlspecialchars($post['title']) ?></h4></a>
<a href="<?=Url::to(['site/post', 'postId' => $post['id'], 'page' => Yii::$app->request->get('page', 1)])?>"><?= htmlspecialchars($post['title']) ?></a>

<a href="<?=Url::to(['site/post', 'postId' => $post['id'], 'page' => isset($_GET['page']) ? $_GET['page'] : 1])?>" class="btn btn-primary btn-sm mb-3">See post</a>
</div>
<?php endforeach; ?>
<?php endif; ?>

<div class="mt-4 d-flex justify-content-center">
<?php echo LinkPager::widget([
'pagination' => $pagination,
]); ?>
</div>
</div>
</div>
</div>
19 changes: 17 additions & 2 deletions web/css/site.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
main > .container {
main>.container {
padding: 70px 15px 20px;
}

Expand All @@ -8,7 +8,7 @@ main > .container {
height: 60px;
}

.footer > .container {
.footer>.container {
padding-right: 15px;
padding-left: 15px;
}
Expand All @@ -28,3 +28,18 @@ main > .container {
margin-top: 5px;
color: #999;
}

a.post-title {
color: #333;
text-decoration: none;
}

.pagination a,
.pagination span {
padding: 5px;
font-size: 20px;
}

.pagination .active a {
font-weight: bold;
}