Mark-Lasfar
feat(seo): add complete dynamic sitemap system with posts, jobs, templates, pages, tags, images, and comments
bee235c | const StoreShortcode = require("../models/StoreShortcode"); | |
| /** | |
| * خدمة معالجة الأكواد المختصرة | |
| * @version 2.0.0 | |
| */ | |
| class ShortcodeService { | |
| /** | |
| * معالجة النص واستبدال جميع الـ Shortcodes | |
| * @param {string} content - المحتوى الأصلي | |
| * @param {string} storeId - معرف المتجر | |
| * @param {Object} options - خيارات إضافية | |
| * @returns {Promise<string>} - المحتوى بعد المعالجة | |
| */ | |
| static async parseShortcodes(content, storeId, options = {}) { | |
| if (!content || typeof content !== 'string') return content; | |
| // التعبير النمطي للـ Shortcodes | |
| const shortcodeRegex = /\[(\w+)(?:\s+([^\]]*))?\]/g; | |
| let parsedContent = content; | |
| let match; | |
| // تجميع جميع الـ Shortcodes الفريدة | |
| const shortcodesToProcess = new Map(); | |
| while ((match = shortcodeRegex.exec(content)) !== null) { | |
| const name = match[1]; | |
| const attrsStr = match[2] || ''; | |
| if (!shortcodesToProcess.has(name)) { | |
| shortcodesToProcess.set(name, []); | |
| } | |
| shortcodesToProcess.get(name).push({ | |
| fullMatch: match[0], | |
| attrs: this.parseAttributes(attrsStr) | |
| }); | |
| } | |
| // معالجة كل Shortcode | |
| for (const [name, occurrences] of shortcodesToProcess) { | |
| const shortcode = await StoreShortcode.findOne({ | |
| userId: storeId, | |
| name: name, | |
| isActive: true | |
| }); | |
| if (!shortcode) continue; | |
| for (const occ of occurrences) { | |
| try { | |
| const html = await shortcode.render(occ.attrs, { | |
| storeId, | |
| userToken: options.userToken | |
| }); | |
| parsedContent = parsedContent.replace(occ.fullMatch, html); | |
| } catch (error) { | |
| console.error(`Error processing shortcode ${name}:`, error); | |
| parsedContent = parsedContent.replace(occ.fullMatch, | |
| `<div class="shortcode-error text-red-500 p-2 border border-red-300 rounded"> | |
| Error: ${error.message} | |
| </div>` | |
| ); | |
| } | |
| } | |
| } | |
| return parsedContent; | |
| } | |
| /** | |
| * تحليل معاملات الـ Shortcode | |
| * @param {string} attrsStr - نص المعاملات | |
| * @returns {Object} - كائن المعاملات | |
| */ | |
| static parseAttributes(attrsStr) { | |
| const attrs = {}; | |
| if (!attrsStr) return attrs; | |
| // دعم كل من name="value" و name=value | |
| const attrRegex = /(\w+)(?:="([^"]*)"|='([^']*)'|="([^"]*)"|="([^"]*)"|="([^"]*)"|=([^\s]+))/g; | |
| let match; | |
| while ((match = attrRegex.exec(attrsStr)) !== null) { | |
| const name = match[1]; | |
| let value = match[2] || match[3] || match[4] || match[5] || match[6] || match[7] || ''; | |
| // تحويل القيم إلى الأنواع المناسبة | |
| if (value === 'true') value = true; | |
| else if (value === 'false') value = false; | |
| else if (!isNaN(value) && value !== '') value = Number(value); | |
| attrs[name] = value; | |
| } | |
| return attrs; | |
| } | |
| /** | |
| * الحصول على قائمة جميع الـ Shortcodes المتاحة لمستخدم | |
| * @param {string} userId - معرف المستخدم | |
| * @returns {Promise<Array>} - قائمة الـ Shortcodes | |
| */ | |
| static async getAllShortcodes(userId) { | |
| const builtins = StoreShortcode.getBuiltinShortcodes(); | |
| const custom = await StoreShortcode.find({ userId, type: 'custom', isActive: true }); | |
| return [ | |
| ...builtins.map(b => ({ ...b, isBuiltin: true })), | |
| ...custom.map(c => ({ ...c.toObject(), isBuiltin: false })) | |
| ]; | |
| } | |
| /** | |
| * إنشاء Shortcode مخصص جديد | |
| * @param {string} userId - معرف المستخدم | |
| * @param {Object} data - بيانات الـ Shortcode | |
| * @returns {Promise<Object>} - الـ Shortcode المنشأ | |
| */ | |
| static async createCustomShortcode(userId, data) { | |
| const { name, displayName, description, template, parameters, settings } = data; | |
| // التحقق من عدم وجود اسم مكرر | |
| const existing = await StoreShortcode.findOne({ userId, name }); | |
| if (existing) { | |
| throw new Error(`Shortcode "${name}" already exists`); | |
| } | |
| const shortcode = new StoreShortcode({ | |
| userId, | |
| name: name.toLowerCase().replace(/[^a-z0-9_-]/g, ''), | |
| displayName, | |
| description, | |
| template, | |
| parameters: parameters || [], | |
| settings: settings || {}, | |
| type: 'custom' | |
| }); | |
| await shortcode.save(); | |
| return shortcode; | |
| } | |
| /** | |
| * تحديث Shortcode مخصص | |
| * @param {string} shortcodeId - معرف الـ Shortcode | |
| * @param {string} userId - معرف المستخدم (للتحقق) | |
| * @param {Object} data - بيانات التحديث | |
| * @returns {Promise<Object>} - الـ Shortcode المحدث | |
| */ | |
| static async updateCustomShortcode(shortcodeId, userId, data) { | |
| const shortcode = await StoreShortcode.findOne({ _id: shortcodeId, userId }); | |
| if (!shortcode) { | |
| throw new Error('Shortcode not found'); | |
| } | |
| Object.assign(shortcode, data); | |
| shortcode.cachedOutput = null; // إبطال التخزين المؤقت | |
| await shortcode.save(); | |
| return shortcode; | |
| } | |
| /** | |
| * حذف Shortcode مخصص | |
| * @param {string} shortcodeId - معرف الـ Shortcode | |
| * @param {string} userId - معرف المستخدم | |
| * @returns {Promise<boolean>} | |
| */ | |
| static async deleteCustomShortcode(shortcodeId, userId) { | |
| const result = await StoreShortcode.deleteOne({ _id: shortcodeId, userId, type: 'custom' }); | |
| return result.deletedCount > 0; | |
| } | |
| /** | |
| * معاينة Shortcode (دون حفظ) | |
| * @param {Object} data - بيانات الـ Shortcode | |
| * @param {Object} context - السياق | |
| * @returns {Promise<string>} - HTML المعاينة | |
| */ | |
| static async previewShortcode(data, context) { | |
| const { name, displayName, template, parameters, attrs = {} } = data; | |
| // دمج المعاملات مع القيم الافتراضية | |
| const mergedAttrs = {}; | |
| if (parameters) { | |
| for (const param of parameters) { | |
| mergedAttrs[param.name] = attrs[param.name] !== undefined | |
| ? attrs[param.name] | |
| : param.defaultValue; | |
| } | |
| } | |
| // تنفيذ القالب | |
| let html = template || ''; | |
| for (const [key, value] of Object.entries(mergedAttrs)) { | |
| const regex = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g'); | |
| html = html.replace(regex, String(value ?? '')); | |
| } | |
| return html || `<div class="p-4 text-gray-500">Preview: ${displayName} (no content)</div>`; | |
| } | |
| /** | |
| * تهيئة الـ Shortcodes لمستخدم جديد | |
| * @param {string} userId - معرف المستخدم | |
| */ | |
| static async initializeUserShortcodes(userId) { | |
| await StoreShortcode.initializeBuiltins(userId); | |
| } | |
| } | |
| module.exports = ShortcodeService; |