Commit 5b2dcf44bcbc4104982dafd29e39da7190924e3b
1 parent
486a3601d8
Exists in
master
and in
1 other branch
Редактор страниц и SEO, CKEditorContoller, CompanyController
Showing 18 changed files with 788 additions and 22 deletions Side-by-side Diff
- app/Classes/Meta.php
- app/Http/Controllers/Admin/CompanyController.php
- app/Http/Controllers/CKEditorController.php
- app/Http/Controllers/PagesController.php
- app/Http/Requests/PagesRequest.php
- app/Http/Requests/SEORequest.php
- app/Models/pages.php
- resources/views/admin/editbloks/index.blade.php
- resources/views/admin/pages/add.blade.php
- resources/views/admin/pages/edit.blade.php
- resources/views/admin/pages/form.blade.php
- resources/views/admin/pages/index.blade.php
- resources/views/admin/reclames/index.blade.php
- resources/views/admin/seo/add.blade.php
- resources/views/admin/seo/edit.blade.php
- resources/views/admin/seo/form.blade.php
- resources/views/admin/seo/index.blade.php
- routes/web.php
app/Classes/Meta.php
... | ... | @@ -0,0 +1,260 @@ |
1 | +<?php | |
2 | + | |
3 | + | |
4 | +namespace App\Classes; | |
5 | + | |
6 | + | |
7 | +class Meta | |
8 | +{ | |
9 | + private static $STATUS = array( | |
10 | + "initialized"=>0, | |
11 | + "success"=>1, | |
12 | + "fail"=>2 | |
13 | + ); | |
14 | + private $metaData; | |
15 | + private $url; | |
16 | + private $html; | |
17 | + private $status; | |
18 | + private $meta; | |
19 | + | |
20 | + /** | |
21 | + * __construct | |
22 | + * | |
23 | + * Require the url of the webpage for meta data extraction | |
24 | + * | |
25 | + * @access public | |
26 | + * @param string $url | |
27 | + */ | |
28 | + public function __construct($url) { | |
29 | + $this->url = $url; | |
30 | + $this->metaData = new \StdClass; | |
31 | + $this->metaData->title = ""; | |
32 | + $this->metaData->description = ""; | |
33 | + $this->metaData->keywords = ""; | |
34 | + $this->metaData->image = ""; | |
35 | + $this->initialized(); | |
36 | + } | |
37 | + | |
38 | + /** | |
39 | + * parse | |
40 | + * | |
41 | + * Parse the meta data from the given url and populate data member $metaData | |
42 | + * | |
43 | + * @access public | |
44 | + * @return integer | |
45 | + */ | |
46 | + public function parse() { | |
47 | + // load HTML as DOMDocument object for parsing | |
48 | + $this->html = new \DOMDocument; | |
49 | + libxml_use_internal_errors(true); | |
50 | + $this->html->loadHTML(file_get_contents($this->url)); | |
51 | + | |
52 | + // php built-in get_meta_tags() only read those with name "title", "description" and so on | |
53 | + // so I wrote my own version supporting twitter:title, og:title, etc. | |
54 | + $this->meta = $this->my_get_meta_tags($this->url); | |
55 | + | |
56 | + $this->success(); // assume successful | |
57 | + | |
58 | + // possible to add more method such as getAuthor() | |
59 | + $this->getTitle(); | |
60 | + $this->getDescription(); | |
61 | + $this->getKeywords(); | |
62 | + $this->getImage(); | |
63 | + | |
64 | + return $this->status; | |
65 | + } | |
66 | + | |
67 | + | |
68 | + /** | |
69 | + * finalize | |
70 | + * | |
71 | + * Export the meta data parsed | |
72 | + * | |
73 | + * @access public | |
74 | + * @return StdClass | |
75 | + */ | |
76 | + public function finalize() { | |
77 | + $tmp = new \StdClass; | |
78 | + $tmp->url = $this->url; | |
79 | + $tmp->title = $this->metaData->title; | |
80 | + $tmp->description = $this->metaData->description; | |
81 | + $tmp->image = $this->metaData->image; | |
82 | + $tmp->status = $this->status; | |
83 | + $tmp->keywords = $this->metaData->keywords; | |
84 | + return $tmp; | |
85 | + } | |
86 | + | |
87 | + /** | |
88 | + * my_get_meta_tags | |
89 | + * | |
90 | + * Require the url to be parsed, read every meta tags found | |
91 | + * | |
92 | + * @access private | |
93 | + * @param string $url | |
94 | + * @return array | |
95 | + */ | |
96 | + private function my_get_meta_tags($url) { | |
97 | + $metatags = $this->html->getElementsByTagName("meta"); | |
98 | + $tmeta = array(); | |
99 | + for ($i=0; $i<$metatags->length; ++$i) { | |
100 | + $item = $metatags->item($i); | |
101 | + $name = $item->getAttribute('name'); | |
102 | + | |
103 | + if (empty($name)) { | |
104 | + // og meta tags, or twitter meta tags | |
105 | + $tmeta[$item->getAttribute('property')] = $item->getAttribute('content'); | |
106 | + } | |
107 | + else { | |
108 | + // conventional meta tags | |
109 | + $tmeta[$name] = $item->getAttribute('content'); | |
110 | + } | |
111 | + } | |
112 | + return $tmeta; | |
113 | + } | |
114 | + | |
115 | + /** | |
116 | + * initizlized | |
117 | + * | |
118 | + * Set the state of the object to be initizlied | |
119 | + * | |
120 | + * @access private | |
121 | + */ | |
122 | + private function initialized() { | |
123 | + $this->status = self::$STATUS["initialized"]; | |
124 | + } | |
125 | + | |
126 | + /** | |
127 | + * success | |
128 | + * | |
129 | + * Set the state of the object to be successful | |
130 | + * | |
131 | + * @access private | |
132 | + */ | |
133 | + private function success() { | |
134 | + $this->status = self::$STATUS["success"]; | |
135 | + } | |
136 | + | |
137 | + /** | |
138 | + * fail | |
139 | + * | |
140 | + * Set the state of the object to be failed | |
141 | + * | |
142 | + * @access private | |
143 | + */ | |
144 | + private function fail() { | |
145 | + $this->status = self::$STATUS["fail"]; | |
146 | + } | |
147 | + | |
148 | + /** | |
149 | + * getTitle | |
150 | + * | |
151 | + * Read meta title based on priorities of the tag name/property, | |
152 | + * fallback to reading <title> and <h1> if meta title not present | |
153 | + * | |
154 | + * @access private | |
155 | + */ | |
156 | + private function getTitle() { | |
157 | + if (isset($this->meta["og:title"])) { | |
158 | + $this->metaData->title = $this->meta["og:title"]; | |
159 | + return; | |
160 | + } | |
161 | + | |
162 | + if (isset($this->meta["twitter:title"])) { | |
163 | + $this->metaData->title = $this->meta["twitter:title"]; | |
164 | + return; | |
165 | + } | |
166 | + | |
167 | + if (isset($this->meta["title"])) { | |
168 | + $this->metaData->title = $this->meta["title"]; | |
169 | + return; | |
170 | + } | |
171 | + | |
172 | + $title = $this->html->getElementsByTagName("title") or $title = $this->html->getElementsByTagName("h1"); | |
173 | + // taking either the title or h1 tag | |
174 | + if (!$title->length) { | |
175 | + // if no h1 tag, nothing good enough to be the site title | |
176 | + $this->fail(); | |
177 | + return; | |
178 | + } | |
179 | + else { | |
180 | + $this->metaData->title = ($title->length) ? $title->item(0)->nodeValue : ""; | |
181 | + } | |
182 | + } | |
183 | + | |
184 | + /** | |
185 | + * getDescription | |
186 | + * | |
187 | + * Read meta description based on priorities of the tag name/property. | |
188 | + * No fallback, it doesn't read anything except for the meta tag | |
189 | + * | |
190 | + * @access private | |
191 | + */ | |
192 | + private function getDescription() { | |
193 | + if (isset($this->meta["og:description"])) { | |
194 | + $this->metaData->description = $this->meta["og:description"]; | |
195 | + return; | |
196 | + } | |
197 | + | |
198 | + if (isset($this->meta["twitter:description"])) { | |
199 | + $this->metaData->description = $this->meta["twitter:description"]; | |
200 | + return; | |
201 | + } | |
202 | + | |
203 | + if (isset($this->meta["description"])) { | |
204 | + $this->metaData->description = $this->meta["description"]; | |
205 | + return; | |
206 | + } | |
207 | + | |
208 | + $this->fail(); | |
209 | + return; | |
210 | + } | |
211 | + | |
212 | + | |
213 | + private function getKeywords() { | |
214 | + if (isset($this->meta["og:keywords"])) { | |
215 | + $this->metaData->keywords = $this->meta["og:keywords"]; | |
216 | + return; | |
217 | + } | |
218 | + | |
219 | + if (isset($this->meta["twitter:keywords"])) { | |
220 | + $this->metaData->keywords = $this->meta["twitter:description"]; | |
221 | + return; | |
222 | + } | |
223 | + | |
224 | + if (isset($this->meta["keywords"])) { | |
225 | + $this->metaData->keywords = $this->meta["keywords"]; | |
226 | + return; | |
227 | + } | |
228 | + | |
229 | + $this->fail(); | |
230 | + return; | |
231 | + } | |
232 | + | |
233 | + | |
234 | + /** | |
235 | + * getImage | |
236 | + * | |
237 | + * Read meta image url based on priorities of the tag name/property. | |
238 | + * No fallback, it doesn't read anything except for the meta tag | |
239 | + * | |
240 | + * @access private | |
241 | + */ | |
242 | + private function getImage() { | |
243 | + if (isset($this->meta["og:image"])) { | |
244 | + $this->metaData->image = $this->meta["og:image"]; | |
245 | + return; | |
246 | + } | |
247 | + | |
248 | + if (isset($this->meta["twitter:image"])) { | |
249 | + $this->metaData->image = $this->meta["twitter:image"]; | |
250 | + return; | |
251 | + } | |
252 | + | |
253 | + if (isset($this->meta["image"])) { | |
254 | + $this->metaData->image = $this->meta["image"]; | |
255 | + return; | |
256 | + } | |
257 | + | |
258 | + $this->fail(); | |
259 | + } | |
260 | +} |
app/Http/Controllers/Admin/CompanyController.php
... | ... | @@ -3,11 +3,16 @@ |
3 | 3 | namespace App\Http\Controllers\Admin; |
4 | 4 | |
5 | 5 | use App\Http\Controllers\Controller; |
6 | +use App\Http\Requests\PagesRequest; | |
7 | +use App\Http\Requests\SEORequest; | |
6 | 8 | use App\Models\Employer; |
7 | 9 | use App\Models\employers_main; |
10 | +use App\Models\header_footer; | |
8 | 11 | use App\Models\Job_title; |
9 | 12 | use App\Models\job_titles_main; |
10 | 13 | use App\Models\pages; |
14 | +use App\Models\reclame; | |
15 | +use App\Models\SEO; | |
11 | 16 | use Illuminate\Http\Request; |
12 | 17 | |
13 | 18 | class CompanyController extends Controller |
... | ... | @@ -17,11 +22,6 @@ class CompanyController extends Controller |
17 | 22 | return; |
18 | 23 | } |
19 | 24 | |
20 | - // кабинет - редактор шапки-футера сайта | |
21 | - public function editblocks() { | |
22 | - return; | |
23 | - } | |
24 | - | |
25 | 25 | // кабинет - редактор должности на главной |
26 | 26 | public function job_titles_main(Request $request) { |
27 | 27 | if ($request->ajax()) { |
... | ... | @@ -40,6 +40,12 @@ class CompanyController extends Controller |
40 | 40 | } |
41 | 41 | } |
42 | 42 | |
43 | + // кабинет - редактор шапки-футера сайта | |
44 | + public function editblocks() { | |
45 | + $header_footer = header_footer::query()->OrderBy('name')->paginate(15); | |
46 | + return view('admin.editbloks.index', compact('header_footer')); | |
47 | + } | |
48 | + | |
43 | 49 | // кабинет - редактор работодатели на главной |
44 | 50 | public function employers_main(Request $request) { |
45 | 51 | if ($request->ajax()) { |
... | ... | @@ -58,11 +64,36 @@ class CompanyController extends Controller |
58 | 64 | } |
59 | 65 | } |
60 | 66 | |
61 | - // кабинет - редактор seo-сайта | |
67 | + //////////// кабинет - редактор seo-сайта ///////////////////////////// | |
62 | 68 | public function editor_seo() { |
63 | - return; | |
69 | + $pages = SEO::query()->OrderBy('url')->paginate(15); | |
70 | + return view('admin.seo.index', compact('pages')); | |
64 | 71 | } |
65 | 72 | |
73 | + public function editor_seo_add() { | |
74 | + return view('admin.seo.add'); | |
75 | + } | |
76 | + | |
77 | + public function editor_seo_store(SEORequest $request) { | |
78 | + SEO::create($request->all()); | |
79 | + return redirect()->route('admin.editor-seo'); | |
80 | + } | |
81 | + | |
82 | + public function editor_seo_edit(SEO $page) { | |
83 | + return view('admin.seo.edit', compact('page')); | |
84 | + } | |
85 | + | |
86 | + public function editor_seo_update(SEORequest $request, SEO $page) { | |
87 | + $page->update($request->all()); | |
88 | + return redirect()->route('admin.editor-seo'); | |
89 | + } | |
90 | + | |
91 | + public function editor_seo_destroy(SEO $page) { | |
92 | + $page->delete(); | |
93 | + return redirect()->route('admin.editor-seo'); | |
94 | + } | |
95 | + /////////////////////////////////////////////////////////////////////// | |
96 | + | |
66 | 97 | /////////// кабинет - редактор страниц //////////////////////////////// |
67 | 98 | public function editor_pages() { |
68 | 99 | $pages = pages::query()->OrderBy('name')->paginate(15); |
... | ... | @@ -73,25 +104,29 @@ class CompanyController extends Controller |
73 | 104 | return view('admin.pages.add'); |
74 | 105 | } |
75 | 106 | |
76 | - public function editor_pages_store(Request $request) { | |
77 | - return; | |
107 | + public function editor_pages_store(PagesRequest $request) { | |
108 | + pages::create($request->all()); | |
109 | + return redirect()->route('admin.editor-pages'); | |
78 | 110 | } |
79 | 111 | |
80 | 112 | public function editor_pages_edit(pages $page) { |
81 | 113 | return view('admin.pages.edit', compact('page')); |
82 | 114 | } |
83 | 115 | |
84 | - public function editor_pages_update(Request $request, pages $page) { | |
85 | - return; | |
116 | + public function editor_pages_update(PagesRequest $request, pages $page) { | |
117 | + $page->update($request->all()); | |
118 | + return redirect()->route('admin.editor-pages'); | |
86 | 119 | } |
87 | 120 | |
88 | 121 | public function editor_pages_destroy(pages $page) { |
89 | - return; | |
122 | + $page->delete(); | |
123 | + return redirect()->route('admin.editor-pages'); | |
90 | 124 | } |
91 | 125 | /////////////////////////////////////////////////////////////////// |
92 | 126 | |
93 | 127 | // кабинет - реклама сайта |
94 | 128 | public function reclames() { |
95 | - return; | |
129 | + $reclames = reclame::query()->OrderBy('title')->paginate(15); | |
130 | + return view('admin.reclames.index', compact('reclames')); | |
96 | 131 | } |
97 | 132 | } |
app/Http/Controllers/CKEditorController.php
... | ... | @@ -0,0 +1,26 @@ |
1 | +<?php | |
2 | + | |
3 | +namespace App\Http\Controllers; | |
4 | + | |
5 | +use Illuminate\Http\Request; | |
6 | + | |
7 | +class CKEditorController extends Controller | |
8 | +{ | |
9 | + public function upload(Request $request) | |
10 | + { | |
11 | + if($request->hasFile('upload')) { | |
12 | + $originName = $request->file('upload')->getClientOriginalName(); | |
13 | + $fileName = pathinfo($originName, PATHINFO_FILENAME); | |
14 | + $extension = $request->file('upload')->getClientOriginalExtension(); | |
15 | + $fileName = $fileName.'_'.time().'.'.$extension; | |
16 | + $request->file('upload')->move(public_path('images'), $fileName); | |
17 | + $CKEditorFuncNum = $request->input('CKEditorFuncNum'); | |
18 | + $url = asset('images/'.$fileName); | |
19 | + $msg = 'Image successfully uploaded'; | |
20 | + $response = "<script>window.parent.CKEDITOR.tools.callFunction($CKEditorFuncNum, '$url', '$msg')</script>"; | |
21 | + | |
22 | + @header('Content-type: text/html; charset=utf-8'); | |
23 | + echo $response; | |
24 | + } | |
25 | + } | |
26 | +} |
app/Http/Controllers/PagesController.php
... | ... | @@ -2,11 +2,13 @@ |
2 | 2 | |
3 | 3 | namespace App\Http\Controllers; |
4 | 4 | |
5 | +use App\Models\pages; | |
5 | 6 | use Illuminate\Http\Request; |
6 | 7 | |
7 | 8 | class PagesController extends Controller |
8 | 9 | { |
9 | - public function pages(string $slug) { | |
10 | - return; | |
10 | + public function pages(pages $pages) { | |
11 | + $page = pages::query()->where('slug', $pages->slug)->first(); | |
12 | + print_r($page); | |
11 | 13 | } |
12 | 14 | } |
app/Http/Requests/PagesRequest.php
... | ... | @@ -13,7 +13,7 @@ class PagesRequest extends FormRequest |
13 | 13 | */ |
14 | 14 | public function authorize() |
15 | 15 | { |
16 | - return false; | |
16 | + return true; | |
17 | 17 | } |
18 | 18 | |
19 | 19 | /** |
... | ... | @@ -23,8 +23,67 @@ class PagesRequest extends FormRequest |
23 | 23 | */ |
24 | 24 | public function rules() |
25 | 25 | { |
26 | + $unique = 'unique:pages,slug'; | |
27 | + if (in_array($this->route()->getName(), ['admin.update-page'])) { | |
28 | + // получаем модель Pages через маршрут admin/editor-pages/edit/{page} | |
29 | + $model = $this->route('page'); | |
30 | + /* | |
31 | + * Проверка на уникальность slug, исключая этот пост по идентификатору: | |
32 | + * 1. posts - таблица базы данных, где проверяется уникальность | |
33 | + * 2. slug - имя колонки, уникальность значения которой проверяется | |
34 | + * 3. значение по которому из проверки исключается запись таблицы БД | |
35 | + * 4. поле, по которому из проверки исключается запись таблицы БД | |
36 | + * Для проверки будет использован такой SQL-запрос к базе данных: | |
37 | + * SELECT COUNT(*) FROM `pages` WHERE `slug` = '...' AND `id` <> 17 | |
38 | + */ | |
39 | + $unique = 'unique:pages,slug,'.$model->id.',id'; | |
40 | + } | |
41 | + | |
26 | 42 | return [ |
27 | - // | |
43 | + 'name' => [ | |
44 | + 'required', | |
45 | + 'string', | |
46 | + 'min:3', | |
47 | + 'max:255', | |
48 | + ], | |
49 | + 'slug' => [ | |
50 | + 'required', | |
51 | + 'max:255', | |
52 | + $unique, | |
53 | + 'regex:~^[-_a-z0-9]+$~i', | |
54 | + ], | |
55 | + 'anons' => [ | |
56 | + 'required', | |
57 | + 'min:500', | |
58 | + ], | |
59 | + 'text' => [ | |
60 | + 'required', | |
61 | + 'min:500', | |
62 | + ], | |
63 | + 'image' => [ | |
64 | + 'mimes:jpeg,jpg,png', | |
65 | + 'max:15000' | |
66 | + ], | |
67 | + ]; | |
68 | + } | |
69 | + | |
70 | + public function messages() { | |
71 | + return [ | |
72 | + 'required' => 'Поле :attribute обязательно для ввода', | |
73 | + 'unique' => 'Поле :attribute должно быть уникальным', | |
74 | + 'mimes' => 'Допускаются файлы только с расширением jpeg,jpg,png', | |
75 | + 'min' => [ | |
76 | + 'string' => 'Поле «:attribute» должно быть не меньше :min символов', | |
77 | + 'integer' => 'Поле «:attribute» должно быть :min или больше', | |
78 | + 'file' => 'Файл «:attribute» должен быть не меньше :min Кбайт' | |
79 | + ], | |
80 | + | |
81 | + 'max' => [ | |
82 | + 'string' => 'Поле «:attribute» должно быть не больше :max символов', | |
83 | + 'integer' => 'Поле «:attribute» должно быть :max или меньше', | |
84 | + 'file' => 'Файл «:attribute» должен быть не больше :max Кбайт' | |
85 | + ], | |
86 | + | |
28 | 87 | ]; |
29 | 88 | } |
30 | 89 | } |
app/Http/Requests/SEORequest.php
... | ... | @@ -0,0 +1,30 @@ |
1 | +<?php | |
2 | + | |
3 | +namespace App\Http\Requests; | |
4 | + | |
5 | +use Illuminate\Foundation\Http\FormRequest; | |
6 | + | |
7 | +class SEORequest extends FormRequest | |
8 | +{ | |
9 | + /** | |
10 | + * Determine if the user is authorized to make this request. | |
11 | + * | |
12 | + * @return bool | |
13 | + */ | |
14 | + public function authorize() | |
15 | + { | |
16 | + return false; | |
17 | + } | |
18 | + | |
19 | + /** | |
20 | + * Get the validation rules that apply to the request. | |
21 | + * | |
22 | + * @return array<string, mixed> | |
23 | + */ | |
24 | + public function rules() | |
25 | + { | |
26 | + return [ | |
27 | + // | |
28 | + ]; | |
29 | + } | |
30 | +} |
app/Models/pages.php
resources/views/admin/editbloks/index.blade.php
... | ... | @@ -0,0 +1,95 @@ |
1 | +@extends('layout.admin', ['title' => 'Админка - Редактор шапки-футера сайта']) | |
2 | + | |
3 | +@section('script') | |
4 | + | |
5 | +@endsection | |
6 | + | |
7 | +@section('search') | |
8 | + <!--<div class="absolute inset-y-0 flex items-center pl-2"> | |
9 | + <svg | |
10 | + class="w-4 h-4" | |
11 | + aria-hidden="true" | |
12 | + fill="currentColor" | |
13 | + viewBox="0 0 20 20" | |
14 | + > | |
15 | + <path | |
16 | + fill-rule="evenodd" | |
17 | + d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" | |
18 | + clip-rule="evenodd" | |
19 | + ></path> | |
20 | + </svg> | |
21 | + </div> | |
22 | + <form action="" method="POST"> | |
23 | + <div style="float:left;"><input | |
24 | + class="w-full pl-8 pr-2 text-sm text-gray-700 placeholder-gray-600 bg-gray-100 border-0 rounded-md dark:placeholder-gray-500 dark:focus:shadow-outline-gray dark:focus:placeholder-gray-600 dark:bg-gray-700 dark:text-gray-200 focus:placeholder-gray-500 focus:bg-white focus:border-purple-300 focus:outline-none focus:shadow-outline-purple form-input" | |
25 | + style="width: 400px" | |
26 | + type="text" | |
27 | + placeholder="Искать..." | |
28 | + aria-label="Search" | |
29 | + /></div> | |
30 | + <div style="float: left"> | |
31 | + <button type="submit" class="px-3 py-1 rounded-md focus:outline-none focus:shadow-outline-purple">Искать</button> | |
32 | + </div> | |
33 | + </form>--> | |
34 | +@endsection | |
35 | + | |
36 | +@section('content') | |
37 | + | |
38 | + <a href="{{ route('admin.add-seo') }}" style="width: 145px" class="px-3 py-1 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-md active:bg-purple-600 hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple"> | |
39 | + Добавить опцию | |
40 | + </a> | |
41 | + <br> | |
42 | + <div class="w-full overflow-hidden rounded-lg shadow-xs" id="ajax_block"> | |
43 | + | |
44 | + <div class="w-full overflow-x-auto"> | |
45 | + <table class="w-full whitespace-no-wrap"> | |
46 | + <thead> | |
47 | + <tr | |
48 | + class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800" | |
49 | + > | |
50 | + <th class="px-4 py-3">№</th> | |
51 | + <th class="px-4 py-3">Название</th> | |
52 | + <th class="px-4 py-3">Ссылка</th> | |
53 | + <th class="px-4 py-3">Категория</th> | |
54 | + <th class="px-4 py-3">Шапка</th> | |
55 | + <th class="px-4 py-3">Дата создания</th> | |
56 | + <th class="px-4 py-3">Редактировать</th> | |
57 | + </tr> | |
58 | + </thead> | |
59 | + <tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800"> | |
60 | + @foreach($header_footer as $page) | |
61 | + <tr class="text-gray-700 dark:text-gray-400"> | |
62 | + <td class="px-4 py-3"> | |
63 | + {{$page->id}} | |
64 | + </td> | |
65 | + <td class="px-4 py-3"> | |
66 | + {{$page->name}} | |
67 | + </td> | |
68 | + <td class="px-4 py-3"> | |
69 | + {{$page->link}} | |
70 | + </td> | |
71 | + <td class="px-4 py-3"> | |
72 | + {{$page->category}} ({{$page->code_id}}) | |
73 | + </td> | |
74 | + <td class="px-4 py-3"> | |
75 | + {{$page->created_at}} | |
76 | + </td> | |
77 | + <td class="px-4 py-3 text-sm_"> | |
78 | + <form action="{{ route('admin.delete-seo', ['page' => $page->id]) }}" method="POST"> | |
79 | + <a href="{{ route('admin.edit-seo', ['page' => $page->id]) }}">Изменить</a> | | |
80 | + @csrf | |
81 | + @method('DELETE') | |
82 | + <input class="btn btn-danger" type="submit" value="Удалить"/> | |
83 | + </form> | |
84 | + </td> | |
85 | + </tr> | |
86 | + @endforeach | |
87 | + </tbody> | |
88 | + </table> | |
89 | + </div> | |
90 | + | |
91 | + <div class="grid px-4 py-3 text-xs font-semibold tracking-wide text-gray-500 uppercase border-t dark:border-gray-700 bg-gray-50 sm:grid-cols-9 dark:text-gray-400 dark:bg-gray-800"> | |
92 | + <?=$header_footer->appends($_GET)->links('admin.pagginate'); ?> | |
93 | + </div> | |
94 | + </div> | |
95 | +@endsection |
resources/views/admin/pages/add.blade.php
1 | 1 | @extends('layout.admin', ['title' => 'Админка - Добавление страницы']) |
2 | 2 | |
3 | 3 | @section('content') |
4 | - <form method="POST" action="{{ route('admin.add-page-store') }}"> | |
4 | + <form method="POST" action="{{ route('admin.add-page-store') }}" enctype="multipart/form-data"> | |
5 | 5 | @include('admin.pages.form') |
6 | 6 | </form> |
7 | 7 | @endsection |
resources/views/admin/pages/edit.blade.php
1 | 1 | @extends('layout.admin', ['title' => 'Админка - Редактирование страницы']) |
2 | 2 | |
3 | 3 | @section('content') |
4 | - <form method="POST" action="{{ route('admin.update-page', ['page' => $page->id]) }}"> | |
4 | + <form method="POST" action="{{ route('admin.update-page', ['page' => $page->id]) }}" enctype="multipart/form-data"> | |
5 | 5 | @include('admin.pages.form') |
6 | 6 | </form> |
7 | 7 | @endsection |
resources/views/admin/pages/form.blade.php
... | ... | @@ -4,6 +4,56 @@ |
4 | 4 | @method('PUT') |
5 | 5 | @endisset |
6 | 6 | |
7 | +<script src="//cdn.ckeditor.com/4.14.0/standard/ckeditor.js"></script> | |
8 | +<script> | |
9 | + CKEDITOR.replace( 'anons'); | |
10 | + CKEDITOR.replace( 'text', { | |
11 | + filebrowserUploadUrl: "{{route('ckeditor.image-upload', ['_token' => csrf_token() ])}}", | |
12 | + filebrowserUploadMethod: 'form' | |
13 | + }); | |
14 | +</script> | |
15 | +<script> | |
16 | + function translit(word){ | |
17 | + var answer = ''; | |
18 | + var converter = { | |
19 | + 'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', | |
20 | + 'е': 'e', 'ё': 'e', 'ж': 'zh', 'з': 'z', 'и': 'i', | |
21 | + 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n', | |
22 | + 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', | |
23 | + 'у': 'u', 'ф': 'f', 'х': 'h', 'ц': 'c', 'ч': 'ch', | |
24 | + 'ш': 'sh', 'щ': 'sch', 'ь': '', 'ы': 'y', 'ъ': '', | |
25 | + 'э': 'e', 'ю': 'yu', 'я': 'ya', | |
26 | + | |
27 | + 'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', | |
28 | + 'Е': 'E', 'Ё': 'E', 'Ж': 'Zh', 'З': 'Z', 'И': 'I', | |
29 | + 'Й': 'Y', 'К': 'K', 'Л': 'L', 'М': 'M', 'Н': 'N', | |
30 | + 'О': 'O', 'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T', | |
31 | + 'У': 'U', 'Ф': 'F', 'Х': 'H', 'Ц': 'C', 'Ч': 'Ch', | |
32 | + 'Ш': 'Sh', 'Щ': 'Sch', 'Ь': '', 'Ы': 'Y', 'Ъ': '', | |
33 | + 'Э': 'E', 'Ю': 'Yu', 'Я': 'Ya', ' ': '-' | |
34 | + }; | |
35 | + | |
36 | + for (var i = 0; i < word.length; ++i ) { | |
37 | + if (converter[word[i]] == undefined){ | |
38 | + answer += word[i]; | |
39 | + } else { | |
40 | + answer += converter[word[i]]; | |
41 | + } | |
42 | + } | |
43 | + | |
44 | + return answer; | |
45 | + } | |
46 | + | |
47 | + window.addEventListener("DOMContentLoaded", (event) => { | |
48 | + let title = document.querySelector('#name'); | |
49 | + let text = document.querySelector('#slug'); | |
50 | + | |
51 | + title.addEventListener('input', function() { | |
52 | + text.value = translit(this.value); | |
53 | + }); | |
54 | + }); | |
55 | + | |
56 | +</script> | |
7 | 57 | <div class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800"> |
8 | 58 | |
9 | 59 | <label class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800"> |
... | ... | @@ -69,8 +119,9 @@ |
69 | 119 | </label><br> |
70 | 120 | |
71 | 121 | <label class="block text-sm"> |
72 | - <input type="file" class="form-control-file" name="image" accept="image/png, image/jpeg"> | |
73 | - </label> | |
122 | + <span class="text-gray-700 dark:text-gray-400">Картинка</span> | |
123 | + <input type="file" class="block w-full mt-1 text-sm dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-300 dark:focus:shadow-outline-gray form-input" id="image" name="image" accept="image/png, image/jpeg"> | |
124 | + </label><br> | |
74 | 125 | |
75 | 126 | <div class="flex flex-col flex-wrap mb-4 space-y-4 md:flex-row md:items-end md:space-x-4"> |
76 | 127 | <div> |
resources/views/admin/pages/index.blade.php
... | ... | @@ -64,7 +64,7 @@ |
64 | 64 | {{$page->name}} |
65 | 65 | </td> |
66 | 66 | <td class="px-4 py-3"> |
67 | - {{ action([\App\Http\Controllers\PagesController::class, 'pages'], ['slug' => $page->slug]) }} | |
67 | + <a target="blank" href="{{ route('page', ['pages' => $page]) }}">{{ route('page', ['pages' => $page]) }}</a> | |
68 | 68 | </td> |
69 | 69 | <td class="px-4 py-3"> |
70 | 70 | {{$page->created_at}} |
resources/views/admin/reclames/index.blade.php
... | ... | @@ -0,0 +1,98 @@ |
1 | +@extends('layout.admin', ['title' => 'Админка - Реклама сайта']) | |
2 | + | |
3 | +@section('script') | |
4 | + | |
5 | +@endsection | |
6 | + | |
7 | +@section('search') | |
8 | + <!--<div class="absolute inset-y-0 flex items-center pl-2"> | |
9 | + <svg | |
10 | + class="w-4 h-4" | |
11 | + aria-hidden="true" | |
12 | + fill="currentColor" | |
13 | + viewBox="0 0 20 20" | |
14 | + > | |
15 | + <path | |
16 | + fill-rule="evenodd" | |
17 | + d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" | |
18 | + clip-rule="evenodd" | |
19 | + ></path> | |
20 | + </svg> | |
21 | + </div> | |
22 | + <form action="" method="POST"> | |
23 | + <div style="float:left;"><input | |
24 | + class="w-full pl-8 pr-2 text-sm text-gray-700 placeholder-gray-600 bg-gray-100 border-0 rounded-md dark:placeholder-gray-500 dark:focus:shadow-outline-gray dark:focus:placeholder-gray-600 dark:bg-gray-700 dark:text-gray-200 focus:placeholder-gray-500 focus:bg-white focus:border-purple-300 focus:outline-none focus:shadow-outline-purple form-input" | |
25 | + style="width: 400px" | |
26 | + type="text" | |
27 | + placeholder="Искать..." | |
28 | + aria-label="Search" | |
29 | + /></div> | |
30 | + <div style="float: left"> | |
31 | + <button type="submit" class="px-3 py-1 rounded-md focus:outline-none focus:shadow-outline-purple">Искать</button> | |
32 | + </div> | |
33 | + </form>--> | |
34 | +@endsection | |
35 | + | |
36 | +@section('content') | |
37 | + | |
38 | + <a href="{{ route('admin.add-seo') }}" style="width: 160px" class="px-3 py-1 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-md active:bg-purple-600 hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple"> | |
39 | + Добавить рекламу | |
40 | + </a> | |
41 | + <br> | |
42 | + <div class="w-full overflow-hidden rounded-lg shadow-xs" id="ajax_block"> | |
43 | + | |
44 | + <div class="w-full overflow-x-auto"> | |
45 | + <table class="w-full whitespace-no-wrap"> | |
46 | + <thead> | |
47 | + <tr | |
48 | + class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800" | |
49 | + > | |
50 | + <th class="px-4 py-3">№</th> | |
51 | + <th class="px-4 py-3">Заголовок</th> | |
52 | + <th class="px-4 py-3">Ссылка</th> | |
53 | + <th class="px-4 py-3">Позиция</th> | |
54 | + <th class="px-4 py-3">Скрыть</th> | |
55 | + <th class="px-4 py-3">Клики</th> | |
56 | + <th class="px-4 py-3">Редактировать</th> | |
57 | + </tr> | |
58 | + </thead> | |
59 | + <tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800"> | |
60 | + @foreach($reclames as $reclame) | |
61 | + <tr class="text-gray-700 dark:text-gray-400"> | |
62 | + <td class="px-4 py-3"> | |
63 | + {{$reclame->id}} | |
64 | + </td> | |
65 | + <td class="px-4 py-3"> | |
66 | + {{$reclame->title}} | |
67 | + </td> | |
68 | + <td class="px-4 py-3"> | |
69 | + {{$reclame->link}} | |
70 | + </td> | |
71 | + <td class="px-4 py-3"> | |
72 | + {{$reclame->position}} | |
73 | + </td> | |
74 | + <td class="px-4 py-3"> | |
75 | + {{$reclame->is_hidden}} | |
76 | + </td> | |
77 | + <td class="px-4 py-3"> | |
78 | + {{$reclame->col_vo_click}} | |
79 | + </td> | |
80 | + <td class="px-4 py-3 text-sm_"> | |
81 | + <form action="{{ route('admin.delete-seo', ['page' => $reclame->id]) }}" method="POST"> | |
82 | + <a href="{{ route('admin.edit-seo', ['page' => $reclame->id]) }}">Изменить</a> | | |
83 | + @csrf | |
84 | + @method('DELETE') | |
85 | + <input class="btn btn-danger" type="submit" value="Удалить"/> | |
86 | + </form> | |
87 | + </td> | |
88 | + </tr> | |
89 | + @endforeach | |
90 | + </tbody> | |
91 | + </table> | |
92 | + </div> | |
93 | + | |
94 | + <div class="grid px-4 py-3 text-xs font-semibold tracking-wide text-gray-500 uppercase border-t dark:border-gray-700 bg-gray-50 sm:grid-cols-9 dark:text-gray-400 dark:bg-gray-800"> | |
95 | + <?=$reclames->appends($_GET)->links('admin.pagginate'); ?> | |
96 | + </div> | |
97 | + </div> | |
98 | +@endsection |
resources/views/admin/seo/add.blade.php
resources/views/admin/seo/edit.blade.php
resources/views/admin/seo/form.blade.php
resources/views/admin/seo/index.blade.php
... | ... | @@ -0,0 +1,90 @@ |
1 | +@extends('layout.admin', ['title' => 'Админка - Страницы SEO сайта']) | |
2 | + | |
3 | +@section('script') | |
4 | + | |
5 | +@endsection | |
6 | + | |
7 | +@section('search') | |
8 | + <!--<div class="absolute inset-y-0 flex items-center pl-2"> | |
9 | + <svg | |
10 | + class="w-4 h-4" | |
11 | + aria-hidden="true" | |
12 | + fill="currentColor" | |
13 | + viewBox="0 0 20 20" | |
14 | + > | |
15 | + <path | |
16 | + fill-rule="evenodd" | |
17 | + d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" | |
18 | + clip-rule="evenodd" | |
19 | + ></path> | |
20 | + </svg> | |
21 | + </div> | |
22 | + <form action="" method="POST"> | |
23 | + <div style="float:left;"><input | |
24 | + class="w-full pl-8 pr-2 text-sm text-gray-700 placeholder-gray-600 bg-gray-100 border-0 rounded-md dark:placeholder-gray-500 dark:focus:shadow-outline-gray dark:focus:placeholder-gray-600 dark:bg-gray-700 dark:text-gray-200 focus:placeholder-gray-500 focus:bg-white focus:border-purple-300 focus:outline-none focus:shadow-outline-purple form-input" | |
25 | + style="width: 400px" | |
26 | + type="text" | |
27 | + placeholder="Искать..." | |
28 | + aria-label="Search" | |
29 | + /></div> | |
30 | + <div style="float: left"> | |
31 | + <button type="submit" class="px-3 py-1 rounded-md focus:outline-none focus:shadow-outline-purple">Искать</button> | |
32 | + </div> | |
33 | + </form>--> | |
34 | +@endsection | |
35 | + | |
36 | +@section('content') | |
37 | + | |
38 | + <a href="{{ route('admin.add-seo') }}" style="width: 200px" class="px-3 py-1 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-md active:bg-purple-600 hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple"> | |
39 | + Добавить seo странице | |
40 | + </a> | |
41 | + <br> | |
42 | + <div class="w-full overflow-hidden rounded-lg shadow-xs" id="ajax_block"> | |
43 | + | |
44 | + <div class="w-full overflow-x-auto"> | |
45 | + <table class="w-full whitespace-no-wrap"> | |
46 | + <thead> | |
47 | + <tr | |
48 | + class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800" | |
49 | + > | |
50 | + <th class="px-4 py-3">№</th> | |
51 | + <th class="px-4 py-3">URL страницы</th> | |
52 | + <th class="px-4 py-3">title страницы</th> | |
53 | + <th class="px-4 py-3">Дата создания</th> | |
54 | + <th class="px-4 py-3">Редактировать</th> | |
55 | + </tr> | |
56 | + </thead> | |
57 | + <tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800"> | |
58 | + @foreach($pages as $page) | |
59 | + <tr class="text-gray-700 dark:text-gray-400"> | |
60 | + <td class="px-4 py-3"> | |
61 | + {{$page->id}} | |
62 | + </td> | |
63 | + <td class="px-4 py-3"> | |
64 | + {{$page->url}} | |
65 | + </td> | |
66 | + <td class="px-4 py-3"> | |
67 | + {{$page->title}} | |
68 | + </td> | |
69 | + <td class="px-4 py-3"> | |
70 | + {{$page->created_at}} | |
71 | + </td> | |
72 | + <td class="px-4 py-3 text-sm_"> | |
73 | + <form action="{{ route('admin.delete-seo', ['page' => $page->id]) }}" method="POST"> | |
74 | + <a href="{{ route('admin.edit-seo', ['page' => $page->id]) }}">Изменить</a> | | |
75 | + @csrf | |
76 | + @method('DELETE') | |
77 | + <input class="btn btn-danger" type="submit" value="Удалить"/> | |
78 | + </form> | |
79 | + </td> | |
80 | + </tr> | |
81 | + @endforeach | |
82 | + </tbody> | |
83 | + </table> | |
84 | + </div> | |
85 | + | |
86 | + <div class="grid px-4 py-3 text-xs font-semibold tracking-wide text-gray-500 uppercase border-t dark:border-gray-700 bg-gray-50 sm:grid-cols-9 dark:text-gray-400 dark:bg-gray-800"> | |
87 | + <?=$pages->appends($_GET)->links('admin.pagginate'); ?> | |
88 | + </div> | |
89 | + </div> | |
90 | +@endsection |
routes/web.php
... | ... | @@ -9,6 +9,7 @@ use App\Http\Controllers\Admin\UsersController; |
9 | 9 | use App\Http\Controllers\Admin\WorkersController; |
10 | 10 | use App\Http\Controllers\Auth\LoginController; |
11 | 11 | use App\Http\Controllers\Auth\RegisterController; |
12 | +use App\Http\Controllers\CKEditorController; | |
12 | 13 | use App\Models\User; |
13 | 14 | use App\Http\Controllers\MainController; |
14 | 15 | use App\Http\Controllers\HomeController; |
... | ... | @@ -17,6 +18,7 @@ use App\Http\Controllers\Admin\CompanyController; |
17 | 18 | use App\Http\Controllers\Admin\Ad_EmployersController; |
18 | 19 | use App\Http\Controllers\Admin\MsgAnswersController; |
19 | 20 | use App\Http\Controllers\Admin\GroupsController; |
21 | +use App\Http\Controllers\PagesController; | |
20 | 22 | |
21 | 23 | |
22 | 24 | /* |
... | ... | @@ -180,6 +182,11 @@ Route::group([ |
180 | 182 | |
181 | 183 | // кабинет - редактор seo-сайта |
182 | 184 | Route::get('editor-seo', [CompanyController::class, 'editor_seo'])->name('editor-seo'); |
185 | + Route::get('editor-seo/add', [CompanyController::class, 'editor_seo_add'])->name('add-seo'); | |
186 | + Route::post('editor-seo/add', [CompanyController::class, 'editor_seo_store'])->name('add-seo-store'); | |
187 | + Route::get('editor-seo/edit/{page}', [CompanyController::class, 'editor_seo_edit'])->name('edit-seo'); | |
188 | + Route::put('editor-seo/edit/{page}', [CompanyController::class, 'editor_seo_update'])->name('update-seo'); | |
189 | + Route::delete('editor-seo/delete/{page}', [CompanyController::class, 'editor_seo_destroy'])->name('delete-seo'); | |
183 | 190 | |
184 | 191 | |
185 | 192 | // кабинет - редактор страниц |
... | ... | @@ -225,3 +232,7 @@ Route::group([ |
225 | 232 | Route::get('roles', [UsersController::class, 'roles'])->name('roles'); |
226 | 233 | |
227 | 234 | }); |
235 | + | |
236 | +Route::post('ckeditor/upload', [CKEditorController::class, 'upload'])->name('ckeditor.image-upload'); | |
237 | + | |
238 | +Route::get('pages/{pages:slug}', [PagesController::class, 'pages'])->name('page'); |