ines-num lines-num-old"> 100
+    <plugin name="cordova-plugin-splashscreen" spec="5.0.2" />
101
+    <plugin name="cordova-plugin-ionic-webview" spec="^5.0.0" />
102
+    <plugin name="cordova-plugin-ionic-keyboard" spec="^2.0.5" />
103
+</widget>

+ 4 - 1
ionic.config.json

@@ -1,7 +1,10 @@
1 1
 {
2 2
   "name": "farmr3",
3 3
   "integrations": {
4
-    "capacitor": {}
4
+    "capacitor": {
5
+      "enabled": false
6
+    },
7
+    "cordova": {}
5 8
   },
6 9
   "type": "angular"
7 10
 }

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1698 - 17353
package-lock.json


+ 97 - 2
package.json

@@ -19,13 +19,43 @@
19 19
     "@angular/platform-browser": "~13.2.2",
20 20
     "@angular/platform-browser-dynamic": "~13.2.2",
21 21
     "@angular/router": "~13.2.2",
22
+    "@awesome-cordova-plugins/barcode-scanner": "^5.37.1",
23
+    "@awesome-cordova-plugins/core": "^5.37.1",
24
+    "@awesome-cordova-plugins/file-transfer": "^5.37.1",
25
+    "@awesome-cordova-plugins/native-geocoder": "^5.41.0",
26
+    "@awesome-cordova-plugins/push": "^5.37.1",
27
+    "@awesome-cordova-plugins/status-bar": "^5.37.2",
28
+    "@awesome-cordova-plugins/stripe": "^5.37.1",
22 29
     "@capacitor/app": "1.1.1",
23 30
     "@capacitor/core": "3.5.0",
24 31
     "@capacitor/haptics": "1.1.4",
32
+    "@capacitor/ios": "3.5.0",
25 33
     "@capacitor/keyboard": "1.2.2",
26 34
     "@capacitor/status-bar": "1.0.8",
35
+    "@fortawesome/angular-fontawesome": "^0.10.1",
36
+    "@fortawesome/fontawesome-free": "^5.15.4",
37
+    "@fortawesome/fontawesome-svg-core": "^1.2.36",
38
+    "@fortawesome/free-brands-svg-icons": "^5.15.4",
39
+    "@fortawesome/free-regular-svg-icons": "^5.15.4",
40
+    "@fortawesome/free-solid-svg-icons": "^5.15.4",
41
+    "@ionic-native/android-permissions": "^5.30.0",
42
+    "@ionic-native/call-number": "^5.36.0",
43
+    "@ionic-native/core": "^5.36.0",
44
+    "@ionic-native/device": "^5.30.0",
45
+    "@ionic-native/email-composer": "^5.36.0",
46
+    "@ionic-native/facebook": "^5.36.0",
47
+    "@ionic-native/geolocation": "^5.36.0",
48
+    "@ionic-native/http": "^5.36.0",
49
+    "@ionic-native/in-app-browser": "^5.36.0",
50
+    "@ionic-native/native-geocoder": "^5.36.0",
51
+    "@ionic-native/paypal": "^5.30.0",
52
+    "@ionic-native/push": "^5.36.0",
53
+    "@ionic-native/sign-in-with-apple": "^5.36.0",
54
+    "@ionic-native/uid": "^5.30.0",
27 55
     "@ionic/angular": "^6.0.0",
56
+    "@ionic/cordova-builders": "^6.1.0",
28 57
     "rxjs": "~6.6.0",
58
+    "sharp": "^0.30.4",
29 59
     "tslib": "^2.2.0",
30 60
     "zone.js": "~0.11.4"
31 61
   },
@@ -41,11 +71,36 @@
41 71
     "@angular/language-service": "~13.2.2",
42 72
     "@capacitor/cli": "3.5.0",
43 73
     "@ionic/angular-toolkit": "^6.0.0",
74
+    "@ionic/storage-angular": "^3.0.6",
44 75
     "@types/jasmine": "~3.6.0",
45 76
     "@types/jasminewd2": "~2.0.3",
46 77
     "@types/node": "^12.11.1",
47 78
     "@typescript-eslint/eslint-plugin": "5.3.0",
48 79
     "@typescript-eslint/parser": "5.3.0",
80
+    "cordova-ios": "^6.2.0",
81
+    "cordova-plugin-add-swift-support": "^2.0.2",
82
+    "cordova-plugin-advanced-http": "^3.2.2",
83
+    "cordova-plugin-android-permissions": "^1.1.2",
84
+    "cordova-plugin-androidx": "^3.0.0",
85
+    "cordova-plugin-androidx-adapter": "^1.1.3",
86
+    "cordova-plugin-device": "2.0.2",
87
+    "cordova-plugin-email-composer": "^0.10.0",
88
+    "cordova-plugin-facebook-connect": "^3.2.0",
89
+    "cordova-plugin-file": "^6.0.2",
90
+    "cordova-plugin-file-transfer": "^1.7.1",
91
+    "cordova-plugin-geolocation": "^4.1.0",
92
+    "cordova-plugin-googlemaps": "^2.7.1",
93
+    "cordova-plugin-googlemaps-sdk": "github:mapsplugin/cordova-plugin-googlemaps-sdk",
94
+    "cordova-plugin-inappbrowser": "^5.0.0",
95
+    "cordova-plugin-ionic-keyboard": "^2.2.0",
96
+    "cordova-plugin-ionic-webview": "^5.0.0",
97
+    "cordova-plugin-nativegeocoder": "^3.4.1",
98
+    "cordova-plugin-sign-in-with-apple": "^0.1.2",
99
+    "cordova-plugin-splashscreen": "5.0.2",
100
+    "cordova-plugin-statusbar": "2.4.2",
101
+    "cordova-plugin-stripe": "^1.5.3",
102
+    "cordova-plugin-uid": "^1.3.0",
103
+    "cordova-support-google-services": "^1.4.1",
49 104
     "eslint": "^7.6.0",
50 105
     "eslint-plugin-import": "2.22.1",
51 106
     "eslint-plugin-jsdoc": "30.7.6",
@@ -58,9 +113,49 @@
58 113
     "karma-coverage-istanbul-reporter": "~3.0.2",
59 114
     "karma-jasmine": "~4.0.0",
60 115
     "karma-jasmine-html-reporter": "^1.5.0",
116
+    "mx.ferreyra.callnumber": "0.0.2",
61 117
     "protractor": "~7.0.0",
62 118
     "ts-node": "~8.3.0",
63 119
     "typescript": "~4.4.4"
64 120
   },
65
-  "description": "An Ionic project"
66
-}
121
+  "description": "An Ionic project",
122
+  "cordova": {
123
+    "plugins": {
124
+      "cordova-plugin-statusbar": {},
125
+      "cordova-plugin-device": {},
126
+      "cordova-plugin-splashscreen": {},
127
+      "cordova-plugin-ionic-webview": {},
128
+      "cordova-plugin-ionic-keyboard": {},
129
+      "cordova-plugin-geolocation": {},
130
+      "cordova-plugin-nativegeocoder": {
131
+        "LOCATION_WHEN_IN_USE_DESCRIPTION": "Use geocoder service"
132
+      },
133
+      "mx.ferreyra.callnumber": {},
134
+      "cordova-plugin-email-composer": {},
135
+      "call-number": {},
136
+      "cordova-plugin-inappbrowser": {},
137
+      "cordova-plugin-sign-in-with-apple": {},
138
+      "cordova-plugin-androidx": {},
139
+      "cordova-plugin-androidx-adapter": {},
140
+      "phonegap-plugin-push": {},
141
+      "cordova-plugin-file-transfer": {},
142
+      "cordova-plugin-stripe": {},
143
+      "com.paypal.cordova.mobilesdk": {},
144
+      "cordova-plugin-facebook-connect": {
145
+        "APP_ID": "4746807965342529",
146
+        "APP_NAME": "FarmSter",
147
+        "FACEBOOK_URL_SCHEME_SUFFIX": " ",
148
+        "OTHER_APP_SCHEMES": " ",
149
+        "FACEBOOK_AUTO_LOG_APP_EVENTS": "true",
150
+        "FACEBOOK_HYBRID_APP_EVENTS": "false",
151
+        "FACEBOOK_ADVERTISER_ID_COLLECTION": "true",
152
+        "FACEBOOK_ANDROID_SDK_VERSION": "11.3.0",
153
+        "FACEBOOK_IOS_SDK_VERSION": "11.1.0",
154
+        "FACEBOOK_BROWSER_SDK_VERSION": "v11.0"
155
+      }
156
+    },
157
+    "platforms": [
158
+      "ios"
159
+    ]
160
+  }
161
+}

+ 8 - 0
resources/README.md

@@ -0,0 +1,8 @@
1
+These are Cordova resources. You can replace icon.png and splash.png and run
2
+`ionic cordova resources` to generate custom icons and splash screens for your
3
+app. See `ionic cordova resources --help` for details.
4
+
5
+Cordova reference documentation:
6
+
7
+- Icons: https://cordova.apache.org/docs/en/latest/config_ref/images.html
8
+- Splash Screens: https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-splashscreen/

BIN
resources/android/icon/drawable-hdpi-icon.png


BIN
resources/android/icon/drawable-ldpi-icon.png


BIN
resources/android/icon/drawable-mdpi-icon.png


BIN
resources/android/icon/drawable-xhdpi-icon.png


BIN
resources/android/icon/drawable-xxhdpi-icon.png


BIN
resources/android/icon/drawable-xxxhdpi-icon.png


BIN
resources/android/splash/drawable-land-hdpi-screen.png


BIN
resources/android/splash/drawable-land-ldpi-screen.png


BIN
resources/android/splash/drawable-land-mdpi-screen.png


BIN
resources/android/splash/drawable-land-xhdpi-screen.png


BIN
resources/android/splash/drawable-land-xxhdpi-screen.png


BIN
resources/android/splash/drawable-land-xxxhdpi-screen.png


BIN
resources/android/splash/drawable-port-hdpi-screen.png


BIN
resources/android/splash/drawable-port-ldpi-screen.png


BIN
resources/android/splash/drawable-port-mdpi-screen.png


BIN
resources/android/splash/drawable-port-xhdpi-screen.png


BIN
resources/android/splash/drawable-port-xxhdpi-screen.png


BIN
resources/android/splash/drawable-port-xxxhdpi-screen.png


+ 7 - 0
resources/android/xml/network_security_config.xml

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<network-security-config>
3
+    <domain-config cleartextTrafficPermitted="true">
4
+        <domain includeSubdomains="true">localhost</domain>
5
+        <domain includeSubdomains="true">tamtime.iamarray.xyz</domain>
6
+    </domain-config>
7
+</network-security-config>

BIN
resources/icon.png


BIN
resources/ios/icon/icon-1024.png


BIN
resources/ios/icon/icon-108@2x.png


BIN
resources/ios/icon/icon-20.png


BIN
resources/ios/icon/icon-20@2x.png


BIN
resources/ios/icon/icon-20@3x.png


BIN
resources/ios/icon/icon-24@2x.png


BIN
resources/ios/icon/icon-27.5@2x.png


BIN
resources/ios/icon/icon-29.png


BIN
resources/ios/icon/icon-29@2x.png


BIN
resources/ios/icon/icon-29@3x.png


BIN
resources/ios/icon/icon-40.png


BIN
resources/ios/icon/icon-40@2x.png


BIN
resources/ios/icon/icon-40@3x.png


BIN
resources/ios/icon/icon-44@2x.png


BIN
resources/ios/icon/icon-50.png


BIN
resources/ios/icon/icon-50@2x.png


BIN
resources/ios/icon/icon-60.png


BIN
resources/ios/icon/icon-60@2x.png


BIN
resources/ios/icon/icon-60@3x.png


BIN
resources/ios/icon/icon-72.png


BIN
resources/ios/icon/icon-72@2x.png


BIN
resources/ios/icon/icon-76.png


BIN
resources/ios/icon/icon-76@2x.png


BIN
resources/ios/icon/icon-83.5@2x.png


BIN
resources/ios/icon/icon-86@2x.png


BIN
resources/ios/icon/icon-98@2x.png


BIN
resources/ios/icon/icon.png


BIN
resources/ios/icon/icon@2x.png


BIN
resources/ios/splash/Default-1792h~iphone.png


BIN
resources/ios/splash/Default-2436h.png


BIN
resources/ios/splash/Default-2688h~iphone.png


BIN
resources/ios/splash/Default-568h@2x~iphone.png


BIN
resources/ios/splash/Default-667h.png


BIN
resources/ios/splash/Default-736h.png


BIN
resources/ios/splash/Default-Landscape-1792h~iphone.png


BIN
resources/ios/splash/Default-Landscape-2436h.png


BIN
resources/ios/splash/Default-Landscape-2688h~iphone.png


BIN
resources/ios/splash/Default-Landscape-736h.png


BIN
resources/ios/splash/Default-Landscape@2x~ipad.png


BIN
resources/ios/splash/Default-Landscape@~ipadpro.png


BIN
resources/ios/splash/Default-Landscape~ipad.png


BIN
resources/ios/splash/Default-Portrait@2x~ipad.png


BIN
resources/ios/splash/Default-Portrait@~ipadpro.png


BIN
resources/ios/splash/Default-Portrait~ipad.png


BIN
resources/ios/splash/Default@2x~iphone.png


BIN
resources/ios/splash/Default@2x~universal~anyany.png


BIN
resources/ios/splash/Default~iphone.png


BIN
resources/splash.png


+ 76 - 0
src/app/app-routing.module.ts

@@ -5,6 +5,82 @@ const routes: Routes = [
5 5
   {
6 6
     path: '',
7 7
     loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule)
8
+  },
9
+  {
10
+    path: 'home',
11
+    loadChildren: () => import('./home/home.module').then( m => m.HomePageModule)
12
+  },
13
+  {
14
+    path: 'register',
15
+    loadChildren: () => import('./register/register.module').then( m => m.RegisterPageModule)
16
+  },
17
+  {
18
+    path: 'login',
19
+    loadChildren: () => import('./login/login.module').then( m => m.LoginPageModule)
20
+  },
21
+  {
22
+    path: 'forgot-password',
23
+    loadChildren: () => import('./forgot-password/forgot-password.module').then( m => m.ForgotPasswordPageModule)
24
+  },
25
+  {
26
+    path: 'register-form',
27
+    loadChildren: () => import('./register-form/register-form.module').then( m => m.RegisterFormPageModule)
28
+  },
29
+  {
30
+    path: 'profile',
31
+    loadChildren: () => import('./profile/profile.module').then( m => m.ProfilePageModule)
32
+  },
33
+  {
34
+    path: 'reset-password',
35
+    loadChildren: () => import('./reset-password/reset-password.module').then( m => m.ResetPasswordPageModule)
36
+  },
37
+  {
38
+    path: 'placedetail',
39
+    loadChildren: () => import('./placedetail/placedetail.module').then( m => m.PlacedetailPageModule)
40
+  },
41
+  {
42
+    path: 'place',
43
+    loadChildren: () => import('./place/place.module').then( m => m.PlacePageModule)
44
+  },
45
+  {
46
+    path: 'province',
47
+    loadChildren: () => import('./province/province.module').then( m => m.ProvincePageModule)
48
+  },
49
+  {
50
+    path: 'gmap',
51
+    loadChildren: () => import('./gmap/gmap.module').then( m => m.GmapPageModule)
52
+  },
53
+  {
54
+    path: 'nearme',
55
+    loadChildren: () => import('./nearme/nearme.module').then( m => m.NearmePageModule)
56
+  },
57
+  {
58
+    path: 'sample',
59
+    loadChildren: () => import('./sample/sample.module').then( m => m.SamplePageModule)
60
+  },
61
+  {
62
+    path: 'product',
63
+    loadChildren: () => import('./product/product.module').then( m => m.ProductPageModule)
64
+  },
65
+  {
66
+    path: 'product-detail',
67
+    loadChildren: () => import('./product-detail/product-detail.module').then( m => m.ProductDetailPageModule)
68
+  },
69
+  {
70
+    path: 'tracking',
71
+    loadChildren: () => import('./tracking/tracking.module').then( m => m.TrackingPageModule)
72
+  },
73
+  {
74
+    path: 'news',
75
+    loadChildren: () => import('./news/news.module').then( m => m.NewsPageModule)
76
+  },
77
+  {
78
+    path: 'checkout',
79
+    loadChildren: () => import('./checkout/checkout.module').then( m => m.CheckoutPageModule)
80
+  },
81
+  {
82
+    path: 'pay',
83
+    loadChildren: () => import('./pay/pay.module').then( m => m.PayPageModule)
8 84
   }
9 85
 ];
10 86
 @NgModule({

+ 4 - 0
src/app/app.component.scss

@@ -0,0 +1,4 @@
1
+.product-title {
2
+    font-weight:bold;
3
+    font-size:20px;
4
+}

+ 43 - 1
src/app/app.component.ts

@@ -1,4 +1,9 @@
1 1
 import { Component } from '@angular/core';
2
+import { Storage } from '@ionic/storage-angular';
3
+import { StatusBar } from '@awesome-cordova-plugins/status-bar/ngx';
4
+import { Uid } from '@ionic-native/uid/ngx';
5
+import { AndroidPermissions } from '@ionic-native/android-permissions/ngx';
6
+
2 7
 
3 8
 @Component({
4 9
   selector: 'app-root',
@@ -6,5 +11,42 @@ import { Component } from '@angular/core';
6 11
   styleUrls: ['app.component.scss'],
7 12
 })
8 13
 export class AppComponent {
9
-  constructor() {}
14
+    constructor(private storage: Storage, private statusBar:StatusBar, private uid: Uid, private androidPermissions: AndroidPermissions) {
15
+
16
+        this.statusBar.overlaysWebView(true);
17
+
18
+
19
+    }
20
+    openQRCode() {
21
+        console.log("open QR Code");
22
+    }
23
+    async ngOnInit() {
24
+        // If using a custom driver:
25
+        // await this.storage.defineDriver(MyCustomDriver)
26
+        await this.storage.create();
27
+        //await this.getImei();
28
+    }
29
+    async getImei() {
30
+        const { hasPermission } = await this.androidPermissions.checkPermission(
31
+            this.androidPermissions.PERMISSION.READ_PHONE_STATE
32
+        );
33
+        console.log(hasPermission);
34
+
35
+        if (!hasPermission) {
36
+            const result = await this.androidPermissions.requestPermission(
37
+                this.androidPermissions.PERMISSION.READ_PHONE_STATE
38
+            );
39
+
40
+            if (!result.hasPermission) {
41
+                throw new Error('Permissions required');
42
+            }
43
+
44
+            // ok, a user gave us permission, we can get him identifiers after restart app
45
+            return;
46
+        }
47
+        console.log("UUID");
48
+        console.log(this.uid.UUID);
49
+        await this.storage.set('uid', this.uid.UUID);
50
+        return this.uid.IMEI
51
+    }
10 52
 }

+ 66 - 6
src/app/app.module.ts

@@ -7,11 +7,71 @@ import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
7 7
 import { AppRoutingModule } from './app-routing.module';
8 8
 import { AppComponent } from './app.component';
9 9
 
10
+import { HttpClientModule, HttpClient} from '@angular/common/http';
11
+
12
+// import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
13
+// import { library } from '@fortawesome/fontawesome-svg-core';
14
+import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
15
+import { fas } from '@fortawesome/free-solid-svg-icons';
16
+import { far } from '@fortawesome/free-regular-svg-icons';
17
+import { fab } from '@fortawesome/free-brands-svg-icons';
18
+
19
+import { Geolocation } from '@ionic-native/geolocation/ngx';
20
+import { NativeGeocoder } from '@ionic-native/native-geocoder/ngx';
21
+
22
+import { CallNumber } from '@ionic-native/call-number/ngx';
23
+import { EmailComposer } from '@ionic-native/email-composer/ngx';
24
+import { HTTP } from '@ionic-native/http/ngx';
25
+import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
26
+
27
+import { IonicStorageModule } from '@ionic/storage-angular';
28
+import { SignInWithApple } from '@ionic-native/sign-in-with-apple/ngx';
29
+
30
+import { PipeModule } from './pipe/pipe.module';
31
+import { Facebook } from '@ionic-native/facebook/ngx';
32
+
33
+import { BarcodeScanner } from '@awesome-cordova-plugins/barcode-scanner/ngx';
34
+import { PayPal } from '@ionic-native/paypal/ngx';
35
+import { StatusBar } from '@awesome-cordova-plugins/status-bar/ngx';
36
+import { Uid } from '@ionic-native/uid/ngx';
37
+import { AndroidPermissions } from '@ionic-native/android-permissions/ngx';
38
+import { Device } from '@ionic-native/device/ngx';
39
+
40
+//import { Stripe } from '@awesome-cordova-plugins/stripe/ngx';
41
+
42
+
43
+// import { IonicImageViewerModule } from 'ionic-img-viewer';
44
+
45
+
46
+// library.add(fas,far,fab);
47
+
10 48
 @NgModule({
11
-  declarations: [AppComponent],
12
-  entryComponents: [],
13
-  imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
14
-  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
15
-  bootstrap: [AppComponent],
49
+    declarations: [AppComponent],
50
+    entryComponents: [],
51
+    imports: [HttpClientModule, BrowserModule, PipeModule,IonicModule.forRoot({_forceStatusbarPadding: true}), AppRoutingModule, FontAwesomeModule,   IonicStorageModule.forRoot()],
52
+    providers: [
53
+        Geolocation,
54
+        NativeGeocoder,
55
+        CallNumber,
56
+        EmailComposer,
57
+        InAppBrowser,
58
+        HTTP,
59
+        Facebook, 
60
+        StatusBar,
61
+        BarcodeScanner,
62
+        SignInWithApple,
63
+        PayPal,
64
+        AndroidPermissions,
65
+        Uid,
66
+        Device,
67
+        //Stripe,
68
+        { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
69
+    ],
70
+    bootstrap: [AppComponent],
16 71
 })
17
-export class AppModule {}
72
+export class AppModule {
73
+    constructor(library: FaIconLibrary) {
74
+        library.addIconPacks(fas, fab, far);
75
+    }
76
+
77
+}

+ 17 - 0
src/app/checkout/checkout-routing.module.ts

@@ -0,0 +1,17 @@
1
+import { NgModule } from '@angular/core';
2
+import { Routes, RouterModule } from '@angular/router';
3
+
4
+import { CheckoutPage } from './checkout.page';
5
+
6
+const routes: Routes = [
7
+  {
8
+    path: '',
9
+    component: CheckoutPage
10
+  }
11
+];
12
+
13
+@NgModule({
14
+  imports: [RouterModule.forChild(routes)],
15
+  exports: [RouterModule],
16
+})
17
+export class CheckoutPageRoutingModule {}

+ 20 - 0
src/app/checkout/checkout.module.ts

@@ -0,0 +1,20 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { FormsModule } from '@angular/forms';
4
+
5
+import { IonicModule } from '@ionic/angular';
6
+
7
+import { CheckoutPageRoutingModule } from './checkout-routing.module';
8
+
9
+import { CheckoutPage } from './checkout.page';
10
+
11
+@NgModule({
12
+  imports: [
13
+    CommonModule,
14
+    FormsModule,
15
+    IonicModule,
16
+    CheckoutPageRoutingModule
17
+  ],
18
+  declarations: [CheckoutPage]
19
+})
20
+export class CheckoutPageModule {}

+ 81 - 0
src/app/checkout/checkout.page.html

@@ -0,0 +1,81 @@
1
+<ion-header>
2
+  <ion-toolbar class='new-background-color'>
3
+    <ion-title>Checkout</ion-title>
4
+      <ion-buttons slot="end">
5
+          <ion-button (click)="dismiss()" color="light">
6
+            <ion-icon name="close-circle-outline"></ion-icon>
7
+          </ion-button>
8
+      </ion-buttons>
9
+  </ion-toolbar>
10
+</ion-header>
11
+
12
+<ion-content>
13
+    <ion-list>
14
+        <ion-list-header>
15
+            Recent Items
16
+        </ion-list-header>
17
+
18
+        <ion-item-sliding>
19
+            <ion-item>
20
+            <ion-avatar slot="start">
21
+                <img src="assets/images/fresh-durian.jpeg">
22
+            </ion-avatar>
23
+            <ion-label>
24
+                <h2>Durian</h2>
25
+                <p>Size M</p>
26
+            </ion-label>
27
+            <ion-label slot='end'>
28
+                3 * 100.00 = 
29
+            </ion-label>
30
+            <ion-label slot='end'>
31
+                300.00 THB
32
+            </ion-label>
33
+            </ion-item>
34
+            <ion-item-options side="end">
35
+                <ion-item-option (click)="unread(item)" color='danger'>Remove</ion-item-option>
36
+            </ion-item-options>
37
+        </ion-item-sliding>
38
+        <ion-item>
39
+            <ion-avatar slot="start">
40
+                <img src="assets/images/fresh-durian.jpeg">
41
+            </ion-avatar>
42
+            <ion-label>
43
+                <h2>Durian</h2>
44
+                <p>Size M</p>
45
+            </ion-label>
46
+            <ion-label slot='end'>
47
+                3 * 100 = 
48
+            </ion-label>
49
+            <ion-label slot='end'>
50
+                300.00 THB
51
+            </ion-label>
52
+        </ion-item>
53
+        <ion-item>
54
+            <ion-label slot="end">
55
+                <h2>Sub Total = </h2>
56
+            </ion-label>
57
+            <ion-label slot='end'>
58
+               600.00 THB
59
+            </ion-label>
60
+        </ion-item>
61
+        <ion-item>
62
+            <ion-label slot="end">
63
+                <h2>Vat 7%  = </h2>
64
+            </ion-label>
65
+            <ion-label slot='end'>
66
+               42.00 THB
67
+            </ion-label>
68
+        </ion-item>
69
+        <ion-item>
70
+            <ion-label slot="end">
71
+                <h2>Total = </h2>
72
+            </ion-label>
73
+            <ion-label slot='end'>
74
+               642.00 THB
75
+            </ion-label>
76
+        </ion-item>
77
+    </ion-list>
78
+    <ion-button expand="full" color="success" (click)="payWithPaypal()">Pay with PayPal</ion-button>
79
+
80
+
81
+</ion-content>

+ 0 - 0
src/app/checkout/checkout.page.scss


+ 24 - 0
src/app/checkout/checkout.page.spec.ts

@@ -0,0 +1,24 @@
1
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
+import { IonicModule } from '@ionic/angular';
3
+
4
+import { CheckoutPage } from './checkout.page';
5
+
6
+describe('CheckoutPage', () => {
7
+  let component: CheckoutPage;
8
+  let fixture: ComponentFixture<CheckoutPage>;
9
+
10
+  beforeEach(waitForAsync(() => {
11
+    TestBed.configureTestingModule({
12
+      declarations: [ CheckoutPage ],
13
+      imports: [IonicModule.forRoot()]
14
+    }).compileComponents();
15
+
16
+    fixture = TestBed.createComponent(CheckoutPage);
17
+    component = fixture.componentInstance;
18
+    fixture.detectChanges();
19
+  }));
20
+
21
+  it('should create', () => {
22
+    expect(component).toBeTruthy();
23
+  });
24
+});

+ 56 - 0
src/app/checkout/checkout.page.ts

@@ -0,0 +1,56 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { ModalController, Platform } from '@ionic/angular';
3
+import { PayPal, PayPalPayment, PayPalConfiguration } from '@ionic-native/paypal/ngx';
4
+
5
+@Component({
6
+    selector: 'app-checkout',
7
+    templateUrl: './checkout.page.html',
8
+    styleUrls: ['./checkout.page.scss'],
9
+})
10
+export class CheckoutPage implements OnInit {
11
+
12
+    constructor(public modalController: ModalController, private payPal: PayPal) { }
13
+
14
+    ngOnInit() {
15
+    }
16
+
17
+    dismiss() {
18
+        // using the injected ModalController this page
19
+        // can "dismiss" itself and optionally pass back data
20
+        this.modalController.dismiss({
21
+            'dismissed': true
22
+        });
23
+    }
24
+
25
+    paymentAmount: string = '640';
26
+    currency: string = 'USD';
27
+    currencyIcon: string = 'USD';
28
+
29
+    payWithPaypal() {
30
+        this.payPal.init({
31
+            PayPalEnvironmentProduction: 'YOUR_PRODUCTION_CLIENT_ID',
32
+            PayPalEnvironmentSandbox: 'AVszPxWCuK3FGXKlgOCFu3gVXr8lFkKj9z1wUo9BYdestZbGRg5vDqWm_pqlLkhEu8KHsjP8NmfPhc_o'
33
+        }).then(() => {
34
+            // Environments: PayPalEnvironmentNoNetwork, PayPalEnvironmentSandbox, PayPalEnvironmentProduction
35
+            this.payPal.prepareToRender('PayPalEnvironmentSandbox', new PayPalConfiguration({
36
+                // Only needed if you get an "Internal Service Error" after PayPal login!
37
+                //payPalShippingAddressOption: 2 // PayPalShippingAddressOptionPayPal
38
+            })).then(() => {
39
+                let payment = new PayPalPayment(this.paymentAmount, this.currency, 'Description', 'sale');
40
+                this.payPal.renderSinglePaymentUI(payment).then((res) => {
41
+                    console.log(res);
42
+                    // Successfully paid
43
+                }, (error) => {
44
+                    console.log(error);
45
+                    // Error or render dialog closed without being successful
46
+                });
47
+            }, (error) => {
48
+                console.log(error);
49
+                // Error in configuration
50
+            });
51
+        }, (error) => {
52
+            console.log(error);
53
+            // Error in initialization, maybe PayPal isn't supported or something else
54
+        });
55
+    }
56
+}

+ 3 - 1
src/app/explore-container/explore-container.component.html

@@ -1,4 +1,6 @@
1 1
 <div id="container">
2 2
   <strong>{{ name }}</strong>
3 3
   <p>Explore <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
4
-</div>
4
+</div>
5
+<ion-title size="large">wellnessroute.info</ion-title>
6
+  <ion-title size="large">แผนที่สุขภาพ</ion-title>

+ 17 - 0
src/app/forgot-password/forgot-password-routing.module.ts

@@ -0,0 +1,17 @@
1
+import { NgModule } from '@angular/core';
2
+import { Routes, RouterModule } from '@angular/router';
3
+
4
+import { ForgotPasswordPage } from './forgot-password.page';
5
+
6
+const routes: Routes = [
7
+  {
8
+    path: '',
9
+    component: ForgotPasswordPage
10
+  }
11
+];
12
+
13
+@NgModule({
14
+  imports: [RouterModule.forChild(routes)],
15
+  exports: [RouterModule],
16
+})
17
+export class ForgotPasswordPageRoutingModule {}

+ 20 - 0
src/app/forgot-password/forgot-password.module.ts

@@ -0,0 +1,20 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { FormsModule } from '@angular/forms';
4
+
5
+import { IonicModule } from '@ionic/angular';
6
+
7
+import { ForgotPasswordPageRoutingModule } from './forgot-password-routing.module';
8
+
9
+import { ForgotPasswordPage } from './forgot-password.page';
10
+
11
+@NgModule({
12
+  imports: [
13
+    CommonModule,
14
+    FormsModule,
15
+    IonicModule,
16
+    ForgotPasswordPageRoutingModule
17
+  ],
18
+  declarations: [ForgotPasswordPage]
19
+})
20
+export class ForgotPasswordPageModule {}

+ 37 - 0
src/app/forgot-password/forgot-password.page.html

@@ -0,0 +1,37 @@
1
+<ion-header>
2
+  <ion-toolbar class="new-background-color">
3
+    <ion-buttons slot="end">
4
+      <ion-button (click)="dismiss()" color="light">ปิด</ion-button>
5
+    </ion-buttons>
6
+    <div class="hcs">
7
+      <img src="/assets/images/wellnessbar.svg" height="50" />    
8
+    </div>
9
+  </ion-toolbar>
10
+</ion-header>
11
+<ion-content>
12
+  <div><img src="/assets/images/image-006.jpg"></div>
13
+  <ion-grid>
14
+    <ion-row>
15
+      <ion-col>
16
+        <div class="ion-text-center">
17
+          <h1>ลืมรหัสผ่าน</h1>
18
+        </div>
19
+      </ion-col>
20
+    </ion-row>
21
+    <form>
22
+      <ion-row>
23
+        <ion-col>
24
+          <ion-item lines="full">
25
+            <ion-label position="floating">อีเมล์</ion-label>
26
+            <ion-input type="email" name="email" [(ngModel)]="email" inputmode="email"></ion-input>
27
+          </ion-item>
28
+        </ion-col>
29
+      </ion-row>
30
+      <ion-row>
31
+        <ion-col>
32
+          <ion-button type="submit" color="danger" expand="block" (click)="resetPassword()">ส่งรหัสผ่าน</ion-button>
33
+        </ion-col>
34
+      </ion-row>
35
+    </form>
36
+  </ion-grid>
37
+</ion-content>

+ 0 - 0
src/app/forgot-password/forgot-password.page.scss


+ 24 - 0
src/app/forgot-password/forgot-password.page.spec.ts

@@ -0,0 +1,24 @@
1
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
+import { IonicModule } from '@ionic/angular';
3
+
4
+import { ForgotPasswordPage } from './forgot-password.page';
5
+
6
+describe('ForgotPasswordPage', () => {
7
+  let component: ForgotPasswordPage;
8
+  let fixture: ComponentFixture<ForgotPasswordPage>;
9
+
10
+  beforeEach(waitForAsync(() => {
11
+    TestBed.configureTestingModule({
12
+      declarations: [ ForgotPasswordPage ],
13
+      imports: [IonicModule.forRoot()]
14
+    }).compileComponents();
15
+
16
+    fixture = TestBed.createComponent(ForgotPasswordPage);
17
+    component = fixture.componentInstance;
18
+    fixture.detectChanges();
19
+  }));
20
+
21
+  it('should create', () => {
22
+    expect(component).toBeTruthy();
23
+  });
24
+});

+ 36 - 0
src/app/forgot-password/forgot-password.page.ts

@@ -0,0 +1,36 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { ModalController } from '@ionic/angular';
3
+import { WpServiceService } from '../wp-service.service';
4
+
5
+@Component({
6
+    selector: 'app-forgot-password',
7
+    templateUrl: './forgot-password.page.html',
8
+    styleUrls: ['./forgot-password.page.scss'],
9
+})
10
+export class ForgotPasswordPage implements OnInit {
11
+
12
+    email = "";
13
+
14
+    constructor(public modalController: ModalController, private wp:WpServiceService) { }
15
+
16
+    ngOnInit() {
17
+    }
18
+
19
+    async resetPassword() {
20
+        if( this.email == "" ) {
21
+            alert("Please Enter Valid Email");
22
+            return;
23
+        }
24
+        let r = await this.wp.resetPassword(this.email);
25
+        alert(r.msg);
26
+    }
27
+
28
+    dismiss() {
29
+        // using the injected ModalController this page
30
+        // can "dismiss" itself and optionally pass back data
31
+        this.modalController.dismiss({
32
+            'dismissed': true
33
+        });
34
+    }
35
+
36
+}

+ 17 - 0
src/app/gmap/gmap-routing.module.ts

@@ -0,0 +1,17 @@
1
+import { NgModule } from '@angular/core';
2
+import { Routes, RouterModule } from '@angular/router';
3
+
4
+import { GmapPage } from './gmap.page';
5
+
6
+const routes: Routes = [
7
+  {
8
+    path: '',
9
+    component: GmapPage
10
+  }
11
+];
12
+
13
+@NgModule({
14
+  imports: [RouterModule.forChild(routes)],
15
+  exports: [RouterModule],
16
+})
17
+export class GmapPageRoutingModule {}

+ 20 - 0
src/app/gmap/gmap.module.ts

@@ -0,0 +1,20 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { FormsModule } from '@angular/forms';
4
+
5
+import { IonicModule } from '@ionic/angular';
6
+
7
+import { GmapPageRoutingModule } from './gmap-routing.module';
8
+
9
+import { GmapPage } from './gmap.page';
10
+
11
+@NgModule({
12
+  imports: [
13
+    CommonModule,
14
+    FormsModule,
15
+    IonicModule,
16
+    GmapPageRoutingModule
17
+  ],
18
+  declarations: [GmapPage]
19
+})
20
+export class GmapPageModule {}

+ 9 - 0
src/app/gmap/gmap.page.html

@@ -0,0 +1,9 @@
1
+<ion-header>
2
+  <ion-toolbar>
3
+    <ion-title>gmap</ion-title>
4
+  </ion-toolbar>
5
+</ion-header>
6
+
7
+<ion-content>..
8
+<div #map id="map"></div>
9
+</ion-content>

+ 3 - 0
src/app/gmap/gmap.page.scss

@@ -0,0 +1,3 @@
1
+#map {
2
+    height: 300px;
3
+}

+ 24 - 0
src/app/gmap/gmap.page.spec.ts

@@ -0,0 +1,24 @@
1
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
+import { IonicModule } from '@ionic/angular';
3
+
4
+import { GmapPage } from './gmap.page';
5
+
6
+describe('GmapPage', () => {
7
+  let component: GmapPage;
8
+  let fixture: ComponentFixture<GmapPage>;
9
+
10
+  beforeEach(waitForAsync(() => {
11
+    TestBed.configureTestingModule({
12
+      declarations: [ GmapPage ],
13
+      imports: [IonicModule.forRoot()]
14
+    }).compileComponents();
15
+
16
+    fixture = TestBed.createComponent(GmapPage);
17
+    component = fixture.componentInstance;
18
+    fixture.detectChanges();
19
+  }));
20
+
21
+  it('should create', () => {
22
+    expect(component).toBeTruthy();
23
+  });
24
+});

+ 72 - 0
src/app/gmap/gmap.page.ts

@@ -0,0 +1,72 @@
1
+import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';
2
+import { ActivatedRoute, Router } from '@angular/router';
3
+import { WpServiceService } from '../wp-service.service';
4
+import { Geolocation } from '@ionic-native/geolocation/ngx';
5
+import { NativeGeocoder, NativeGeocoderResult, NativeGeocoderOptions } from '@ionic-native/native-geocoder/ngx';
6
+
7
+declare var google: any;
8
+
9
+@Component({
10
+  selector: 'app-gmap',
11
+  templateUrl: './gmap.page.html',
12
+  styleUrls: ['./gmap.page.scss'],
13
+})
14
+export class GmapPage implements OnInit {
15
+  @Input() id: string;
16
+  data: any;
17
+  placelist: any;
18
+  latdynamic: any;
19
+  londynamic: any;
20
+  xxx: any;
21
+  yyy: any;
22
+  placeL: any;
23
+  map: any;
24
+  // @ViewChild('map', { static: false }) mapElement: ElementRef;
25
+  @ViewChild('map', { static: true }) mapRef: ElementRef;
26
+  // @ViewChild('map', { static: false }) mapElement: ElementRef;
27
+
28
+
29
+  constructor(private wpservice: WpServiceService, private route: ActivatedRoute, private router: Router, private geolocation: Geolocation) { }
30
+
31
+  ngOnInit() {
32
+    this.showMap();
33
+    // this.loadMap() 
34
+    let id = this.route.snapshot.paramMap.get('id') || this.id;
35
+    this.wpservice.getPlaceDetail(173).subscribe((data) => {
36
+      this.placelist = data;
37
+      this.latdynamic = data['acf']['gmap']['lat'];
38
+      this.londynamic = data['acf']['gmap']['lng'];
39
+
40
+      console.log("load Place Detail ...");
41
+      console.log(this.latdynamic);
42
+      console.log("load Lati ...");
43
+      console.log(this.londynamic);
44
+      console.log("load Lon ...");
45
+      console.log(data);
46
+      let xxx = this.londynamic;
47
+      const yyy = this.latdynamic;
48
+    }, error => {
49
+      console.log("errror ", error);
50
+    });
51
+
52
+    let watch = this.geolocation.watchPosition();
53
+    watch.subscribe((data) => {
54
+      // data can be a set of coordinates, or an error (if an error occurred).
55
+      // data.coords.latitude
56
+      // data.coords.longitude
57
+      console.log(data);
58
+    });
59
+  }
60
+
61
+  showMap() {
62
+    let location = new google.maps.LatLng(13.7643439, 100.6899729);
63
+    let options = {
64
+      center: location,
65
+      zoom: 15,
66
+      disableDefaultUI: true
67
+    }
68
+    this.map = new google.maps.Map(this.mapRef.nativeElement, options);
69
+    //this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
70
+  }
71
+
72
+}

+ 17 - 0
src/app/home/home-routing.module.ts

@@ -0,0 +1,17 @@
1
+import { NgModule } from '@angular/core';
2
+import { Routes, RouterModule } from '@angular/router';
3
+
4
+import { HomePage } from './home.page';
5
+
6
+const routes: Routes = [
7
+  {
8
+    path: '',
9
+    component: HomePage
10
+  }
11
+];
12
+
13
+@NgModule({
14
+  imports: [RouterModule.forChild(routes)],
15
+  exports: [RouterModule],
16
+})
17
+export class HomePageRoutingModule {}

+ 26 - 0
src/app/home/home.module.ts

@@ -0,0 +1,26 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { FormsModule } from '@angular/forms';
4
+
5
+import { IonicModule } from '@ionic/angular';
6
+
7
+import { HomePageRoutingModule } from './home-routing.module';
8
+
9
+import { HomePage } from './home.page';
10
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
11
+import { IonSlides } from '@ionic/angular';
12
+
13
+import { PipeModule } from '../pipe/pipe.module';
14
+
15
+@NgModule({
16
+  imports: [
17
+    CommonModule,
18
+    FormsModule,
19
+    IonicModule,
20
+      PipeModule,
21
+    HomePageRoutingModule,
22
+    FontAwesomeModule
23
+  ],
24
+  declarations: [HomePage]
25
+})
26
+export class HomePageModule {}

+ 0 - 0
src/app/home/home.page.290921.html


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels

tum/whitesports - Gogs: Simplico Git Service

Няма описание

class-wp-posts-list-table.php 58KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034
  1. <?php
  2. /**
  3. * List Table API: WP_Posts_List_Table class
  4. *
  5. * @package WordPress
  6. * @subpackage Administration
  7. * @since 3.1.0
  8. */
  9. /**
  10. * Core class used to implement displaying posts in a list table.
  11. *
  12. * @since 3.1.0
  13. * @access private
  14. *
  15. * @see WP_List_Table
  16. */
  17. class WP_Posts_List_Table extends WP_List_Table {
  18. /**
  19. * Whether the items should be displayed hierarchically or linearly.
  20. *
  21. * @since 3.1.0
  22. * @var bool
  23. */
  24. protected $hierarchical_display;
  25. /**
  26. * Holds the number of pending comments for each post.
  27. *
  28. * @since 3.1.0
  29. * @var array
  30. */
  31. protected $comment_pending_count;
  32. /**
  33. * Holds the number of posts for this user.
  34. *
  35. * @since 3.1.0
  36. * @var int
  37. */
  38. private $user_posts_count;
  39. /**
  40. * Holds the number of posts which are sticky.
  41. *
  42. * @since 3.1.0
  43. * @var int
  44. */
  45. private $sticky_posts_count = 0;
  46. private $is_trash;
  47. /**
  48. * Current level for output.
  49. *
  50. * @since 4.3.0
  51. * @var int
  52. */
  53. protected $current_level = 0;
  54. /**
  55. * Constructor.
  56. *
  57. * @since 3.1.0
  58. *
  59. * @see WP_List_Table::__construct() for more information on default arguments.
  60. *
  61. * @global WP_Post_Type $post_type_object
  62. * @global wpdb $wpdb WordPress database abstraction object.
  63. *
  64. * @param array $args An associative array of arguments.
  65. */
  66. public function __construct( $args = array() ) {
  67. global $post_type_object, $wpdb;
  68. parent::__construct(
  69. array(
  70. 'plural' => 'posts',
  71. 'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
  72. )
  73. );
  74. $post_type = $this->screen->post_type;
  75. $post_type_object = get_post_type_object( $post_type );
  76. $exclude_states = get_post_stati(
  77. array(
  78. 'show_in_admin_all_list' => false,
  79. )
  80. );
  81. $this->user_posts_count = (int) $wpdb->get_var(
  82. $wpdb->prepare(
  83. "SELECT COUNT( 1 )
  84. FROM $wpdb->posts
  85. WHERE post_type = %s
  86. AND post_status NOT IN ( '" . implode( "','", $exclude_states ) . "' )
  87. AND post_author = %d",
  88. $post_type,
  89. get_current_user_id()
  90. )
  91. );
  92. if ( $this->user_posts_count
  93. && ! current_user_can( $post_type_object->cap->edit_others_posts )
  94. && empty( $_REQUEST['post_status'] ) && empty( $_REQUEST['all_posts'] )
  95. && empty( $_REQUEST['author'] ) && empty( $_REQUEST['show_sticky'] )
  96. ) {
  97. $_GET['author'] = get_current_user_id();
  98. }
  99. $sticky_posts = get_option( 'sticky_posts' );
  100. if ( 'post' === $post_type && $sticky_posts ) {
  101. $sticky_posts = implode( ', ', array_map( 'absint', (array) $sticky_posts ) );
  102. $this->sticky_posts_count = (int) $wpdb->get_var(
  103. $wpdb->prepare(
  104. "SELECT COUNT( 1 )
  105. FROM $wpdb->posts
  106. WHERE post_type = %s
  107. AND post_status NOT IN ('trash', 'auto-draft')
  108. AND ID IN ($sticky_posts)",
  109. $post_type
  110. )
  111. );
  112. }
  113. }
  114. /**
  115. * Sets whether the table layout should be hierarchical or not.
  116. *
  117. * @since 4.2.0
  118. *
  119. * @param bool $display Whether the table layout should be hierarchical.
  120. */
  121. public function set_hierarchical_display( $display ) {
  122. $this->hierarchical_display = $display;
  123. }
  124. /**
  125. * @return bool
  126. */
  127. public function ajax_user_can() {
  128. return current_user_can( get_post_type_object( $this->screen->post_type )->cap->edit_posts );
  129. }
  130. /**
  131. * @global string $mode List table view mode.
  132. * @global array $avail_post_stati
  133. * @global WP_Query $wp_query WordPress Query object.
  134. * @global int $per_page
  135. */
  136. public function prepare_items() {
  137. global $mode, $avail_post_stati, $wp_query, $per_page;
  138. if ( ! empty( $_REQUEST['mode'] ) ) {
  139. $mode = 'excerpt' === $_REQUEST['mode'] ? 'excerpt' : 'list';
  140. set_user_setting( 'posts_list_mode', $mode );
  141. } else {
  142. $mode = get_user_setting( 'posts_list_mode', 'list' );
  143. }
  144. // Is going to call wp().
  145. $avail_post_stati = wp_edit_posts_query();
  146. $this->set_hierarchical_display(
  147. is_post_type_hierarchical( $this->screen->post_type )
  148. && 'menu_order title' === $wp_query->query['orderby']
  149. );
  150. $post_type = $this->screen->post_type;
  151. $per_page = $this->get_items_per_page( 'edit_' . $post_type . '_per_page' );
  152. /** This filter is documented in wp-admin/includes/post.php */
  153. $per_page = apply_filters( 'edit_posts_per_page', $per_page, $post_type );
  154. if ( $this->hierarchical_display ) {
  155. $total_items = $wp_query->post_count;
  156. } elseif ( $wp_query->found_posts || $this->get_pagenum() === 1 ) {
  157. $total_items = $wp_query->found_posts;
  158. } else {
  159. $post_counts = (array) wp_count_posts( $post_type, 'readable' );
  160. if ( isset( $_REQUEST['post_status'] ) && in_array( $_REQUEST['post_status'], $avail_post_stati, true ) ) {
  161. $total_items = $post_counts[ $_REQUEST['post_status'] ];
  162. } elseif ( isset( $_REQUEST['show_sticky'] ) && $_REQUEST['show_sticky'] ) {
  163. $total_items = $this->sticky_posts_count;
  164. } elseif ( isset( $_GET['author'] ) && get_current_user_id() === (int) $_GET['author'] ) {
  165. $total_items = $this->user_posts_count;
  166. } else {
  167. $total_items = array_sum( $post_counts );
  168. // Subtract post types that are not included in the admin all list.
  169. foreach ( get_post_stati( array( 'show_in_admin_all_list' => false ) ) as $state ) {
  170. $total_items -= $post_counts[ $state ];
  171. }
  172. }
  173. }
  174. $this->is_trash = isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'];
  175. $this->set_pagination_args(
  176. array(
  177. 'total_items' => $total_items,
  178. 'per_page' => $per_page,
  179. )
  180. );
  181. }
  182. /**
  183. * @return bool
  184. */
  185. public function has_items() {
  186. return have_posts();
  187. }
  188. /**
  189. */
  190. public function no_items() {
  191. if ( isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] ) {
  192. echo get_post_type_object( $this->screen->post_type )->labels->not_found_in_trash;
  193. } else {
  194. echo get_post_type_object( $this->screen->post_type )->labels->not_found;
  195. }
  196. }
  197. /**
  198. * Determine if the current view is the "All" view.
  199. *
  200. * @since 4.2.0
  201. *
  202. * @return bool Whether the current view is the "All" view.
  203. */
  204. protected function is_base_request() {
  205. $vars = $_GET;
  206. unset( $vars['paged'] );
  207. if ( empty( $vars ) ) {
  208. return true;
  209. } elseif ( 1 === count( $vars ) && ! empty( $vars['post_type'] ) ) {
  210. return $this->screen->post_type === $vars['post_type'];
  211. }
  212. return 1 === count( $vars ) && ! empty( $vars['mode'] );
  213. }
  214. /**
  215. * Helper to create links to edit.php with params.
  216. *
  217. * @since 4.4.0
  218. *
  219. * @param string[] $args Associative array of URL parameters for the link.
  220. * @param string $label Link text.
  221. * @param string $class Optional. Class attribute. Default empty string.
  222. * @return string The formatted link string.
  223. */
  224. protected function get_edit_link( $args, $label, $class = '' ) {
  225. $url = add_query_arg( $args, 'edit.php' );
  226. $class_html = '';
  227. $aria_current = '';
  228. if ( ! empty( $class ) ) {
  229. $class_html = sprintf(
  230. ' class="%s"',
  231. esc_attr( $class )
  232. );
  233. if ( 'current' === $class ) {
  234. $aria_current = ' aria-current="page"';
  235. }
  236. }
  237. return sprintf(
  238. '<a href="%s"%s%s>%s</a>',
  239. esc_url( $url ),
  240. $class_html,
  241. $aria_current,
  242. $label
  243. );
  244. }
  245. /**
  246. * @global array $locked_post_status This seems to be deprecated.
  247. * @global array $avail_post_stati
  248. * @return array
  249. */
  250. protected function get_views() {
  251. global $locked_post_status, $avail_post_stati;
  252. $post_type = $this->screen->post_type;
  253. if ( ! empty( $locked_post_status ) ) {
  254. return array();
  255. }
  256. $status_links = array();
  257. $num_posts = wp_count_posts( $post_type, 'readable' );
  258. $total_posts = array_sum( (array) $num_posts );
  259. $class = '';
  260. $current_user_id = get_current_user_id();
  261. $all_args = array( 'post_type' => $post_type );
  262. $mine = '';
  263. // Subtract post types that are not included in the admin all list.
  264. foreach ( get_post_stati( array( 'show_in_admin_all_list' => false ) ) as $state ) {
  265. $total_posts -= $num_posts->$state;
  266. }
  267. if ( $this->user_posts_count && $this->user_posts_count !== $total_posts ) {
  268. if ( isset( $_GET['author'] ) && ( $current_user_id === (int) $_GET['author'] ) ) {
  269. $class = 'current';
  270. }
  271. $mine_args = array(
  272. 'post_type' => $post_type,
  273. 'author' => $current_user_id,
  274. );
  275. $mine_inner_html = sprintf(
  276. /* translators: %s: Number of posts. */
  277. _nx(
  278. 'Mine <span class="count">(%s)</span>',
  279. 'Mine <span class="count">(%s)</span>',
  280. $this->user_posts_count,
  281. 'posts'
  282. ),
  283. number_format_i18n( $this->user_posts_count )
  284. );
  285. $mine = $this->get_edit_link( $mine_args, $mine_inner_html, $class );
  286. $all_args['all_posts'] = 1;
  287. $class = '';
  288. }
  289. if ( empty( $class ) && ( $this->is_base_request() || isset( $_REQUEST['all_posts'] ) ) ) {
  290. $class = 'current';
  291. }
  292. $all_inner_html = sprintf(
  293. /* translators: %s: Number of posts. */
  294. _nx(
  295. 'All <span class="count">(%s)</span>',
  296. 'All <span class="count">(%s)</span>',
  297. $total_posts,
  298. 'posts'
  299. ),
  300. number_format_i18n( $total_posts )
  301. );
  302. $status_links['all'] = $this->get_edit_link( $all_args, $all_inner_html, $class );
  303. if ( $mine ) {
  304. $status_links['mine'] = $mine;
  305. }
  306. foreach ( get_post_stati( array( 'show_in_admin_status_list' => true ), 'objects' ) as $status ) {
  307. $class = '';
  308. $status_name = $status->name;
  309. if ( ! in_array( $status_name, $avail_post_stati, true ) || empty( $num_posts->$status_name ) ) {
  310. continue;
  311. }
  312. if ( isset( $_REQUEST['post_status'] ) && $status_name === $_REQUEST['post_status'] ) {
  313. $class = 'current';
  314. }
  315. $status_args = array(
  316. 'post_status' => $status_name,
  317. 'post_type' => $post_type,
  318. );
  319. $status_label = sprintf(
  320. translate_nooped_plural( $status->label_count, $num_posts->$status_name ),
  321. number_format_i18n( $num_posts->$status_name )
  322. );
  323. $status_links[ $status_name ] = $this->get_edit_link( $status_args, $status_label, $class );
  324. }
  325. if ( ! empty( $this->sticky_posts_count ) ) {
  326. $class = ! empty( $_REQUEST['show_sticky'] ) ? 'current' : '';
  327. $sticky_args = array(
  328. 'post_type' => $post_type,
  329. 'show_sticky' => 1,
  330. );
  331. $sticky_inner_html = sprintf(
  332. /* translators: %s: Number of posts. */
  333. _nx(
  334. 'Sticky <span class="count">(%s)</span>',
  335. 'Sticky <span class="count">(%s)</span>',
  336. $this->sticky_posts_count,
  337. 'posts'
  338. ),
  339. number_format_i18n( $this->sticky_posts_count )
  340. );
  341. $sticky_link = array(
  342. 'sticky' => $this->get_edit_link( $sticky_args, $sticky_inner_html, $class ),
  343. );
  344. // Sticky comes after Publish, or if not listed, after All.
  345. $split = 1 + array_search( ( isset( $status_links['publish'] ) ? 'publish' : 'all' ), array_keys( $status_links ), true );
  346. $status_links = array_merge( array_slice( $status_links, 0, $split ), $sticky_link, array_slice( $status_links, $split ) );
  347. }
  348. return $status_links;
  349. }
  350. /**
  351. * @return array
  352. */
  353. protected function get_bulk_actions() {
  354. $actions = array();
  355. $post_type_obj = get_post_type_object( $this->screen->post_type );
  356. if ( current_user_can( $post_type_obj->cap->edit_posts ) ) {
  357. if ( $this->is_trash ) {
  358. $actions['untrash'] = __( 'Restore' );
  359. } else {
  360. $actions['edit'] = __( 'Edit' );
  361. }
  362. }
  363. if ( current_user_can( $post_type_obj->cap->delete_posts ) ) {
  364. if ( $this->is_trash || ! EMPTY_TRASH_DAYS ) {
  365. $actions['delete'] = __( 'Delete permanently' );
  366. } else {
  367. $actions['trash'] = __( 'Move to Trash' );
  368. }
  369. }
  370. return $actions;
  371. }
  372. /**
  373. * Displays a categories drop-down for filtering on the Posts list table.
  374. *
  375. * @since 4.6.0
  376. *
  377. * @global int $cat Currently selected category.
  378. *
  379. * @param string $post_type Post type slug.
  380. */
  381. protected function categories_dropdown( $post_type ) {
  382. global $cat;
  383. /**
  384. * Filters whether to remove the 'Categories' drop-down from the post list table.
  385. *
  386. * @since 4.6.0
  387. *
  388. * @param bool $disable Whether to disable the categories drop-down. Default false.
  389. * @param string $post_type Post type slug.
  390. */
  391. if ( false !== apply_filters( 'disable_categories_dropdown', false, $post_type ) ) {
  392. return;
  393. }
  394. if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
  395. $dropdown_options = array(
  396. 'show_option_all' => get_taxonomy( 'category' )->labels->all_items,
  397. 'hide_empty' => 0,
  398. 'hierarchical' => 1,
  399. 'show_count' => 0,
  400. 'orderby' => 'name',
  401. 'selected' => $cat,
  402. );
  403. echo '<label class="screen-reader-text" for="cat">' . get_taxonomy( 'category' )->labels->filter_by_item . '</label>';
  404. wp_dropdown_categories( $dropdown_options );
  405. }
  406. }
  407. /**
  408. * Displays a formats drop-down for filtering items.
  409. *
  410. * @since 5.2.0
  411. * @access protected
  412. *
  413. * @param string $post_type Post type slug.
  414. */
  415. protected function formats_dropdown( $post_type ) {
  416. /**
  417. * Filters whether to remove the 'Formats' drop-down from the post list table.
  418. *
  419. * @since 5.2.0
  420. * @since 5.5.0 The `$post_type` parameter was added.
  421. *
  422. * @param bool $disable Whether to disable the drop-down. Default false.
  423. * @param string $post_type Post type slug.
  424. */
  425. if ( apply_filters( 'disable_formats_dropdown', false, $post_type ) ) {
  426. return;
  427. }
  428. // Return if the post type doesn't have post formats or if we're in the Trash.
  429. if ( ! is_object_in_taxonomy( $post_type, 'post_format' ) || $this->is_trash ) {
  430. return;
  431. }
  432. // Make sure the dropdown shows only formats with a post count greater than 0.
  433. $used_post_formats = get_terms(
  434. array(
  435. 'taxonomy' => 'post_format',
  436. 'hide_empty' => true,
  437. )
  438. );
  439. // Return if there are no posts using formats.
  440. if ( ! $used_post_formats ) {
  441. return;
  442. }
  443. $displayed_post_format = isset( $_GET['post_format'] ) ? $_GET['post_format'] : '';
  444. ?>
  445. <label for="filter-by-format" class="screen-reader-text"><?php _e( 'Filter by post format' ); ?></label>
  446. <select name="post_format" id="filter-by-format">
  447. <option<?php selected( $displayed_post_format, '' ); ?> value=""><?php _e( 'All formats' ); ?></option>
  448. <?php
  449. foreach ( $used_post_formats as $used_post_format ) {
  450. // Post format slug.
  451. $slug = str_replace( 'post-format-', '', $used_post_format->slug );
  452. // Pretty, translated version of the post format slug.
  453. $pretty_name = get_post_format_string( $slug );
  454. // Skip the standard post format.
  455. if ( 'standard' === $slug ) {
  456. continue;
  457. }
  458. ?>
  459. <option<?php selected( $displayed_post_format, $slug ); ?> value="<?php echo esc_attr( $slug ); ?>"><?php echo esc_html( $pretty_name ); ?></option>
  460. <?php
  461. }
  462. ?>
  463. </select>
  464. <?php
  465. }
  466. /**
  467. * @param string $which
  468. */
  469. protected function extra_tablenav( $which ) {
  470. ?>
  471. <div class="alignleft actions">
  472. <?php
  473. if ( 'top' === $which ) {
  474. ob_start();
  475. $this->months_dropdown( $this->screen->post_type );
  476. $this->categories_dropdown( $this->screen->post_type );
  477. $this->formats_dropdown( $this->screen->post_type );
  478. /**
  479. * Fires before the Filter button on the Posts and Pages list tables.
  480. *
  481. * The Filter button allows sorting by date and/or category on the
  482. * Posts list table, and sorting by date on the Pages list table.
  483. *
  484. * @since 2.1.0
  485. * @since 4.4.0 The `$post_type` parameter was added.
  486. * @since 4.6.0 The `$which` parameter was added.
  487. *
  488. * @param string $post_type The post type slug.
  489. * @param string $which The location of the extra table nav markup:
  490. * 'top' or 'bottom' for WP_Posts_List_Table,
  491. * 'bar' for WP_Media_List_Table.
  492. */
  493. do_action( 'restrict_manage_posts', $this->screen->post_type, $which );
  494. $output = ob_get_clean();
  495. if ( ! empty( $output ) ) {
  496. echo $output;
  497. submit_button( __( 'Filter' ), '', 'filter_action', false, array( 'id' => 'post-query-submit' ) );
  498. }
  499. }
  500. if ( $this->is_trash && $this->has_items()
  501. && current_user_can( get_post_type_object( $this->screen->post_type )->cap->edit_others_posts )
  502. ) {
  503. submit_button( __( 'Empty Trash' ), 'apply', 'delete_all', false );
  504. }
  505. ?>
  506. </div>
  507. <?php
  508. /**
  509. * Fires immediately following the closing "actions" div in the tablenav for the posts
  510. * list table.
  511. *
  512. * @since 4.4.0
  513. *
  514. * @param string $which The location of the extra table nav markup: 'top' or 'bottom'.
  515. */
  516. do_action( 'manage_posts_extra_tablenav', $which );
  517. }
  518. /**
  519. * @return string
  520. */
  521. public function current_action() {
  522. if ( isset( $_REQUEST['delete_all'] ) || isset( $_REQUEST['delete_all2'] ) ) {
  523. return 'delete_all';
  524. }
  525. return parent::current_action();
  526. }
  527. /**
  528. * @global string $mode List table view mode.
  529. *
  530. * @return array
  531. */
  532. protected function get_table_classes() {
  533. global $mode;
  534. $mode_class = esc_attr( 'table-view-' . $mode );
  535. return array(
  536. 'widefat',
  537. 'fixed',
  538. 'striped',
  539. $mode_class,
  540. is_post_type_hierarchical( $this->screen->post_type ) ? 'pages' : 'posts',
  541. );
  542. }
  543. /**
  544. * @return array
  545. */
  546. public function get_columns() {
  547. $post_type = $this->screen->post_type;
  548. $posts_columns = array();
  549. $posts_columns['cb'] = '<input type="checkbox" />';
  550. /* translators: Posts screen column name. */
  551. $posts_columns['title'] = _x( 'Title', 'column name' );
  552. if ( post_type_supports( $post_type, 'author' ) ) {
  553. $posts_columns['author'] = __( 'Author' );
  554. }
  555. $taxonomies = get_object_taxonomies( $post_type, 'objects' );
  556. $taxonomies = wp_filter_object_list( $taxonomies, array( 'show_admin_column' => true ), 'and', 'name' );
  557. /**
  558. * Filters the taxonomy columns in the Posts list table.
  559. *
  560. * The dynamic portion of the hook name, `$post_type`, refers to the post
  561. * type slug.
  562. *
  563. * Possible hook names include:
  564. *
  565. * - `manage_taxonomies_for_post_columns`
  566. * - `manage_taxonomies_for_page_columns`
  567. *
  568. * @since 3.5.0
  569. *
  570. * @param string[] $taxonomies Array of taxonomy names to show columns for.
  571. * @param string $post_type The post type.
  572. */
  573. $taxonomies = apply_filters( "manage_taxonomies_for_{$post_type}_columns", $taxonomies, $post_type );
  574. $taxonomies = array_filter( $taxonomies, 'taxonomy_exists' );
  575. foreach ( $taxonomies as $taxonomy ) {
  576. if ( 'category' === $taxonomy ) {
  577. $column_key = 'categories';
  578. } elseif ( 'post_tag' === $taxonomy ) {
  579. $column_key = 'tags';
  580. } else {
  581. $column_key = 'taxonomy-' . $taxonomy;
  582. }
  583. $posts_columns[ $column_key ] = get_taxonomy( $taxonomy )->labels->name;
  584. }
  585. $post_status = ! empty( $_REQUEST['post_status'] ) ? $_REQUEST['post_status'] : 'all';
  586. if ( post_type_supports( $post_type, 'comments' )
  587. && ! in_array( $post_status, array( 'pending', 'draft', 'future' ), true )
  588. ) {
  589. $posts_columns['comments'] = sprintf(
  590. '<span class="vers comment-grey-bubble" title="%1$s"><span class="screen-reader-text">%2$s</span></span>',
  591. esc_attr__( 'Comments' ),
  592. __( 'Comments' )
  593. );
  594. }
  595. $posts_columns['date'] = __( 'Date' );
  596. if ( 'page' === $post_type ) {
  597. /**
  598. * Filters the columns displayed in the Pages list table.
  599. *
  600. * @since 2.5.0
  601. *
  602. * @param string[] $post_columns An associative array of column headings.
  603. */
  604. $posts_columns = apply_filters( 'manage_pages_columns', $posts_columns );
  605. } else {
  606. /**
  607. * Filters the columns displayed in the Posts list table.
  608. *
  609. * @since 1.5.0
  610. *
  611. * @param string[] $post_columns An associative array of column headings.
  612. * @param string $post_type The post type slug.
  613. */
  614. $posts_columns = apply_filters( 'manage_posts_columns', $posts_columns, $post_type );
  615. }
  616. /**
  617. * Filters the columns displayed in the Posts list table for a specific post type.
  618. *
  619. * The dynamic portion of the hook name, `$post_type`, refers to the post type slug.
  620. *
  621. * Possible hook names include:
  622. *
  623. * - `manage_post_posts_columns`
  624. * - `manage_page_posts_columns`
  625. *
  626. * @since 3.0.0
  627. *
  628. * @param string[] $post_columns An associative array of column headings.
  629. */
  630. return apply_filters( "manage_{$post_type}_posts_columns", $posts_columns );
  631. }
  632. /**
  633. * @return array
  634. */
  635. protected function get_sortable_columns() {
  636. return array(
  637. 'title' => 'title',
  638. 'parent' => 'parent',
  639. 'comments' => 'comment_count',
  640. 'date' => array( 'date', true ),
  641. );
  642. }
  643. /**
  644. * @global WP_Query $wp_query WordPress Query object.
  645. * @global int $per_page
  646. * @param array $posts
  647. * @param int $level
  648. */
  649. public function display_rows( $posts = array(), $level = 0 ) {
  650. global $wp_query, $per_page;
  651. if ( empty( $posts ) ) {
  652. $posts = $wp_query->posts;
  653. }
  654. add_filter( 'the_title', 'esc_html' );
  655. if ( $this->hierarchical_display ) {
  656. $this->_display_rows_hierarchical( $posts, $this->get_pagenum(), $per_page );
  657. } else {
  658. $this->_display_rows( $posts, $level );
  659. }
  660. }
  661. /**
  662. * @param array $posts
  663. * @param int $level
  664. */
  665. private function _display_rows( $posts, $level = 0 ) {
  666. $post_type = $this->screen->post_type;
  667. // Create array of post IDs.
  668. $post_ids = array();
  669. foreach ( $posts as $a_post ) {
  670. $post_ids[] = $a_post->ID;
  671. }
  672. if ( post_type_supports( $post_type, 'comments' ) ) {
  673. $this->comment_pending_count = get_pending_comments_num( $post_ids );
  674. }
  675. foreach ( $posts as $post ) {
  676. $this->single_row( $post, $level );
  677. }
  678. }
  679. /**
  680. * @global wpdb $wpdb WordPress database abstraction object.
  681. * @global WP_Post $post Global post object.
  682. * @param array $pages
  683. * @param int $pagenum
  684. * @param int $per_page
  685. */
  686. private function _display_rows_hierarchical( $pages, $pagenum = 1, $per_page = 20 ) {
  687. global $wpdb;
  688. $level = 0;
  689. if ( ! $pages ) {
  690. $pages = get_pages( array( 'sort_column' => 'menu_order' ) );
  691. if ( ! $pages ) {
  692. return;
  693. }
  694. }
  695. /*
  696. * Arrange pages into two parts: top level pages and children_pages
  697. * children_pages is two dimensional array, eg.
  698. * children_pages[10][] contains all sub-pages whose parent is 10.
  699. * It only takes O( N ) to arrange this and it takes O( 1 ) for subsequent lookup operations
  700. * If searching, ignore hierarchy and treat everything as top level
  701. */
  702. if ( empty( $_REQUEST['s'] ) ) {
  703. $top_level_pages = array();
  704. $children_pages = array();
  705. foreach ( $pages as $page ) {
  706. // Catch and repair bad pages.
  707. if ( $page->post_parent === $page->ID ) {
  708. $page->post_parent = 0;
  709. $wpdb->update( $wpdb->posts, array( 'post_parent' => 0 ), array( 'ID' => $page->ID ) );
  710. clean_post_cache( $page );
  711. }
  712. if ( $page->post_parent > 0 ) {
  713. $children_pages[ $page->post_parent ][] = $page;
  714. } else {
  715. $top_level_pages[] = $page;
  716. }
  717. }
  718. $pages = &$top_level_pages;
  719. }
  720. $count = 0;
  721. $start = ( $pagenum - 1 ) * $per_page;
  722. $end = $start + $per_page;
  723. $to_display = array();
  724. foreach ( $pages as $page ) {
  725. if ( $count >= $end ) {
  726. break;
  727. }
  728. if ( $count >= $start ) {
  729. $to_display[ $page->ID ] = $level;
  730. }
  731. $count++;
  732. if ( isset( $children_pages ) ) {
  733. $this->_page_rows( $children_pages, $count, $page->ID, $level + 1, $pagenum, $per_page, $to_display );
  734. }
  735. }
  736. // If it is the last pagenum and there are orphaned pages, display them with paging as well.
  737. if ( isset( $children_pages ) && $count < $end ) {
  738. foreach ( $children_pages as $orphans ) {
  739. foreach ( $orphans as $op ) {
  740. if ( $count >= $end ) {
  741. break;
  742. }
  743. if ( $count >= $start ) {
  744. $to_display[ $op->ID ] = 0;
  745. }
  746. $count++;
  747. }
  748. }
  749. }
  750. $ids = array_keys( $to_display );
  751. _prime_post_caches( $ids );
  752. if ( ! isset( $GLOBALS['post'] ) ) {
  753. $GLOBALS['post'] = reset( $ids );
  754. }
  755. foreach ( $to_display as $page_id => $level ) {
  756. echo "\t";
  757. $this->single_row( $page_id, $level );
  758. }
  759. }
  760. /**
  761. * Given a top level page ID, display the nested hierarchy of sub-pages
  762. * together with paging support
  763. *
  764. * @since 3.1.0 (Standalone function exists since 2.6.0)
  765. * @since 4.2.0 Added the `$to_display` parameter.
  766. *
  767. * @param array $children_pages
  768. * @param int $count
  769. * @param int $parent
  770. * @param int $level
  771. * @param int $pagenum
  772. * @param int $per_page
  773. * @param array $to_display List of pages to be displayed. Passed by reference.
  774. */
  775. private function _page_rows( &$children_pages, &$count, $parent, $level, $pagenum, $per_page, &$to_display ) {
  776. if ( ! isset( $children_pages[ $parent ] ) ) {
  777. return;
  778. }
  779. $start = ( $pagenum - 1 ) * $per_page;
  780. $end = $start + $per_page;
  781. foreach ( $children_pages[ $parent ] as $page ) {
  782. if ( $count >= $end ) {
  783. break;
  784. }
  785. // If the page starts in a subtree, print the parents.
  786. if ( $count === $start && $page->post_parent > 0 ) {
  787. $my_parents = array();
  788. $my_parent = $page->post_parent;
  789. while ( $my_parent ) {
  790. // Get the ID from the list or the attribute if my_parent is an object.
  791. $parent_id = $my_parent;
  792. if ( is_object( $my_parent ) ) {
  793. $parent_id = $my_parent->ID;
  794. }
  795. $my_parent = get_post( $parent_id );
  796. $my_parents[] = $my_parent;
  797. if ( ! $my_parent->post_parent ) {
  798. break;
  799. }
  800. $my_parent = $my_parent->post_parent;
  801. }
  802. $num_parents = count( $my_parents );
  803. while ( $my_parent = array_pop( $my_parents ) ) {
  804. $to_display[ $my_parent->ID ] = $level - $num_parents;
  805. $num_parents--;
  806. }
  807. }
  808. if ( $count >= $start ) {
  809. $to_display[ $page->ID ] = $level;
  810. }
  811. $count++;
  812. $this->_page_rows( $children_pages, $count, $page->ID, $level + 1, $pagenum, $per_page, $to_display );
  813. }
  814. unset( $children_pages[ $parent ] ); // Required in order to keep track of orphans.
  815. }
  816. /**
  817. * Handles the checkbox column output.
  818. *
  819. * @since 4.3.0
  820. *
  821. * @param WP_Post $post The current WP_Post object.
  822. */
  823. public function column_cb( $post ) {
  824. $show = current_user_can( 'edit_post', $post->ID );
  825. /**
  826. * Filters whether to show the bulk edit checkbox for a post in its list table.
  827. *
  828. * By default the checkbox is only shown if the current user can edit the post.
  829. *
  830. * @since 5.7.0
  831. *
  832. * @param bool $show Whether to show the checkbox.
  833. * @param WP_Post $post The current WP_Post object.
  834. */
  835. if ( apply_filters( 'wp_list_table_show_post_checkbox', $show, $post ) ) :
  836. ?>
  837. <label class="screen-reader-text" for="cb-select-<?php the_ID(); ?>">
  838. <?php
  839. /* translators: %s: Post title. */
  840. printf( __( 'Select %s' ), _draft_or_post_title() );
  841. ?>
  842. </label>
  843. <input id="cb-select-<?php the_ID(); ?>" type="checkbox" name="post[]" value="<?php the_ID(); ?>" />
  844. <div class="locked-indicator">
  845. <span class="locked-indicator-icon" aria-hidden="true"></span>
  846. <span class="screen-reader-text">
  847. <?php
  848. printf(
  849. /* translators: %s: Post title. */
  850. __( '&#8220;%s&#8221; is locked' ),
  851. _draft_or_post_title()
  852. );
  853. ?>
  854. </span>
  855. </div>
  856. <?php
  857. endif;
  858. }
  859. /**
  860. * @since 4.3.0
  861. *
  862. * @param WP_Post $post
  863. * @param string $classes
  864. * @param string $data
  865. * @param string $primary
  866. */
  867. protected function _column_title( $post, $classes, $data, $primary ) {
  868. echo '<td class="' . $classes . ' page-title" ', $data, '>';
  869. echo $this->column_title( $post );
  870. echo $this->handle_row_actions( $post, 'title', $primary );
  871. echo '</td>';
  872. }
  873. /**
  874. * Handles the title column output.
  875. *
  876. * @since 4.3.0
  877. *
  878. * @global string $mode List table view mode.
  879. *
  880. * @param WP_Post $post The current WP_Post object.
  881. */
  882. public function column_title( $post ) {
  883. global $mode;
  884. if ( $this->hierarchical_display ) {
  885. if ( 0 === $this->current_level && (int) $post->post_parent > 0 ) {
  886. // Sent level 0 by accident, by default, or because we don't know the actual level.
  887. $find_main_page = (int) $post->post_parent;
  888. while ( $find_main_page > 0 ) {
  889. $parent = get_post( $find_main_page );
  890. if ( is_null( $parent ) ) {
  891. break;
  892. }
  893. $this->current_level++;
  894. $find_main_page = (int) $parent->post_parent;
  895. if ( ! isset( $parent_name ) ) {
  896. /** This filter is documented in wp-includes/post-template.php */
  897. $parent_name = apply_filters( 'the_title', $parent->post_title, $parent->ID );
  898. }
  899. }
  900. }
  901. }
  902. $can_edit_post = current_user_can( 'edit_post', $post->ID );
  903. if ( $can_edit_post && 'trash' !== $post->post_status ) {
  904. $lock_holder = wp_check_post_lock( $post->ID );
  905. if ( $lock_holder ) {
  906. $lock_holder = get_userdata( $lock_holder );
  907. $locked_avatar = get_avatar( $lock_holder->ID, 18 );
  908. /* translators: %s: User's display name. */
  909. $locked_text = esc_html( sprintf( __( '%s is currently editing' ), $lock_holder->display_name ) );
  910. } else {
  911. $locked_avatar = '';
  912. $locked_text = '';
  913. }
  914. echo '<div class="locked-info"><span class="locked-avatar">' . $locked_avatar . '</span> <span class="locked-text">' . $locked_text . "</span></div>\n";
  915. }
  916. $pad = str_repeat( '&#8212; ', $this->current_level );
  917. echo '<strong>';
  918. $title = _draft_or_post_title();
  919. if ( $can_edit_post && 'trash' !== $post->post_status ) {
  920. printf(
  921. '<a class="row-title" href="%s" aria-label="%s">%s%s</a>',
  922. get_edit_post_link( $post->ID ),
  923. /* translators: %s: Post title. */
  924. esc_attr( sprintf( __( '&#8220;%s&#8221; (Edit)' ), $title ) ),
  925. $pad,
  926. $title
  927. );
  928. } else {
  929. printf(
  930. '<span>%s%s</span>',
  931. $pad,
  932. $title
  933. );
  934. }
  935. _post_states( $post );
  936. if ( isset( $parent_name ) ) {
  937. $post_type_object = get_post_type_object( $post->post_type );
  938. echo ' | ' . $post_type_object->labels->parent_item_colon . ' ' . esc_html( $parent_name );
  939. }
  940. echo "</strong>\n";
  941. if ( 'excerpt' === $mode
  942. && ! is_post_type_hierarchical( $this->screen->post_type )
  943. && current_user_can( 'read_post', $post->ID )
  944. ) {
  945. if ( post_password_required( $post ) ) {
  946. echo '<span class="protected-post-excerpt">' . esc_html( get_the_excerpt() ) . '</span>';
  947. } else {
  948. echo esc_html( get_the_excerpt() );
  949. }
  950. }
  951. get_inline_data( $post );
  952. }
  953. /**
  954. * Handles the post date column output.
  955. *
  956. * @since 4.3.0
  957. *
  958. * @global string $mode List table view mode.
  959. *
  960. * @param WP_Post $post The current WP_Post object.
  961. */
  962. public function column_date( $post ) {
  963. global $mode;
  964. if ( '0000-00-00 00:00:00' === $post->post_date ) {
  965. $t_time = __( 'Unpublished' );
  966. $time_diff = 0;
  967. } else {
  968. $t_time = sprintf(
  969. /* translators: 1: Post date, 2: Post time. */
  970. __( '%1$s at %2$s' ),
  971. /* translators: Post date format. See https://www.php.net/manual/datetime.format.php */
  972. get_the_time( __( 'Y/m/d' ), $post ),
  973. /* translators: Post time format. See https://www.php.net/manual/datetime.format.php */
  974. get_the_time( __( 'g:i a' ), $post )
  975. );
  976. $time = get_post_timestamp( $post );
  977. $time_diff = time() - $time;
  978. }
  979. if ( 'publish' === $post->post_status ) {
  980. $status = __( 'Published' );
  981. } elseif ( 'future' === $post->post_status ) {
  982. if ( $time_diff > 0 ) {
  983. $status = '<strong class="error-message">' . __( 'Missed schedule' ) . '</strong>';
  984. } else {
  985. $status = __( 'Scheduled' );
  986. }
  987. } else {
  988. $status = __( 'Last Modified' );
  989. }
  990. /**
  991. * Filters the status text of the post.
  992. *
  993. * @since 4.8.0
  994. *
  995. * @param string $status The status text.
  996. * @param WP_Post $post Post object.
  997. * @param string $column_name The column name.
  998. * @param string $mode The list display mode ('excerpt' or 'list').
  999. */
  1000. $status = apply_filters( 'post_date_column_status', $status, $post, 'date', $mode );
  1001. if ( $status ) {
  1002. echo $status . '<br />';
  1003. }
  1004. /**
  1005. * Filters the published time of the post.
  1006. *
  1007. * @since 2.5.1
  1008. * @since 5.5.0 Removed the difference between 'excerpt' and 'list' modes.
  1009. * The published time and date are both displayed now,
  1010. * which is equivalent to the previous 'excerpt' mode.
  1011. *
  1012. * @param string $t_time The published time.
  1013. * @param WP_Post $post Post object.
  1014. * @param string $column_name The column name.
  1015. * @param string $mode The list display mode ('excerpt' or 'list').
  1016. */
  1017. echo apply_filters( 'post_date_column_time', $t_time, $post, 'date', $mode );
  1018. }
  1019. /**
  1020. * Handles the comments column output.
  1021. *
  1022. * @since 4.3.0
  1023. *
  1024. * @param WP_Post $post The current WP_Post object.
  1025. */
  1026. public function column_comments( $post ) {
  1027. ?>
  1028. <div class="post-com-count-wrapper">
  1029. <?php
  1030. $pending_comments = isset( $this->comment_pending_count[ $post->ID ] ) ? $this->comment_pending_count[ $post->ID ] : 0;
  1031. $this->comments_bubble( $post->ID, $pending_comments );
  1032. ?>
  1033. </div>
  1034. <?php
  1035. }
  1036. /**
  1037. * Handles the post author column output.
  1038. *
  1039. * @since 4.3.0
  1040. *
  1041. * @param WP_Post $post The current WP_Post object.
  1042. */
  1043. public function column_author( $post ) {
  1044. $args = array(
  1045. 'post_type' => $post->post_type,
  1046. 'author' => get_the_author_meta( 'ID' ),
  1047. );
  1048. echo $this->get_edit_link( $args, get_the_author() );
  1049. }
  1050. /**
  1051. * Handles the default column output.
  1052. *
  1053. * @since 4.3.0
  1054. *
  1055. * @param WP_Post $post The current WP_Post object.
  1056. * @param string $column_name The current column name.
  1057. */
  1058. public function column_default( $post, $column_name ) {
  1059. if ( 'categories' === $column_name ) {
  1060. $taxonomy = 'category';
  1061. } elseif ( 'tags' === $column_name ) {
  1062. $taxonomy = 'post_tag';
  1063. } elseif ( 0 === strpos( $column_name, 'taxonomy-' ) ) {
  1064. $taxonomy = substr( $column_name, 9 );
  1065. } else {
  1066. $taxonomy = false;
  1067. }
  1068. if ( $taxonomy ) {
  1069. $taxonomy_object = get_taxonomy( $taxonomy );
  1070. $terms = get_the_terms( $post->ID, $taxonomy );
  1071. if ( is_array( $terms ) ) {
  1072. $term_links = array();
  1073. foreach ( $terms as $t ) {
  1074. $posts_in_term_qv = array();
  1075. if ( 'post' !== $post->post_type ) {
  1076. $posts_in_term_qv['post_type'] = $post->post_type;
  1077. }
  1078. if ( $taxonomy_object->query_var ) {
  1079. $posts_in_term_qv[ $taxonomy_object->query_var ] = $t->slug;
  1080. } else {
  1081. $posts_in_term_qv['taxonomy'] = $taxonomy;
  1082. $posts_in_term_qv['term'] = $t->slug;
  1083. }
  1084. $label = esc_html( sanitize_term_field( 'name', $t->name, $t->term_id, $taxonomy, 'display' ) );
  1085. $term_links[] = $this->get_edit_link( $posts_in_term_qv, $label );
  1086. }
  1087. /**
  1088. * Filters the links in `$taxonomy` column of edit.php.
  1089. *
  1090. * @since 5.2.0
  1091. *
  1092. * @param string[] $term_links Array of term editing links.
  1093. * @param string $taxonomy Taxonomy name.
  1094. * @param WP_Term[] $terms Array of term objects appearing in the post row.
  1095. */
  1096. $term_links = apply_filters( 'post_column_taxonomy_links', $term_links, $taxonomy, $terms );
  1097. /* translators: Used between list items, there is a space after the comma. */
  1098. echo implode( __( ', ' ), $term_links );
  1099. } else {
  1100. echo '<span aria-hidden="true">&#8212;</span><span class="screen-reader-text">' . $taxonomy_object->labels->no_terms . '</span>';
  1101. }
  1102. return;
  1103. }
  1104. if ( is_post_type_hierarchical( $post->post_type ) ) {
  1105. /**
  1106. * Fires in each custom column on the Posts list table.
  1107. *
  1108. * This hook only fires if the current post type is hierarchical,
  1109. * such as pages.
  1110. *
  1111. * @since 2.5.0
  1112. *
  1113. * @param string $column_name The name of the column to display.
  1114. * @param int $post_id The current post ID.
  1115. */
  1116. do_action( 'manage_pages_custom_column', $column_name, $post->ID );
  1117. } else {
  1118. /**
  1119. * Fires in each custom column in the Posts list table.
  1120. *
  1121. * This hook only fires if the current post type is non-hierarchical,
  1122. * such as posts.
  1123. *
  1124. * @since 1.5.0
  1125. *
  1126. * @param string $column_name The name of the column to display.
  1127. * @param int $post_id The current post ID.
  1128. */
  1129. do_action( 'manage_posts_custom_column', $column_name, $post->ID );
  1130. }
  1131. /**
  1132. * Fires for each custom column of a specific post type in the Posts list table.
  1133. *
  1134. * The dynamic portion of the hook name, `$post->post_type`, refers to the post type.
  1135. *
  1136. * Possible hook names include:
  1137. *
  1138. * - `manage_post_posts_custom_column`
  1139. * - `manage_page_posts_custom_column`
  1140. *
  1141. * @since 3.1.0
  1142. *
  1143. * @param string $column_name The name of the column to display.
  1144. * @param int $post_id The current post ID.
  1145. */
  1146. do_action( "manage_{$post->post_type}_posts_custom_column", $column_name, $post->ID );
  1147. }
  1148. /**
  1149. * @global WP_Post $post Global post object.
  1150. *
  1151. * @param int|WP_Post $post
  1152. * @param int $level
  1153. */
  1154. public function single_row( $post, $level = 0 ) {
  1155. $global_post = get_post();
  1156. $post = get_post( $post );
  1157. $this->current_level = $level;
  1158. $GLOBALS['post'] = $post;
  1159. setup_postdata( $post );
  1160. $classes = 'iedit author-' . ( get_current_user_id() === (int) $post->post_author ? 'self' : 'other' );
  1161. $lock_holder = wp_check_post_lock( $post->ID );
  1162. if ( $lock_holder ) {
  1163. $classes .= ' wp-locked';
  1164. }
  1165. if ( $post->post_parent ) {
  1166. $count = count( get_post_ancestors( $post->ID ) );
  1167. $classes .= ' level-' . $count;
  1168. } else {
  1169. $classes .= ' level-0';
  1170. }
  1171. ?>
  1172. <tr id="post-<?php echo $post->ID; ?>" class="<?php echo implode( ' ', get_post_class( $classes, $post->ID ) ); ?>">
  1173. <?php $this->single_row_columns( $post ); ?>
  1174. </tr>
  1175. <?php
  1176. $GLOBALS['post'] = $global_post;
  1177. }
  1178. /**
  1179. * Gets the name of the default primary column.
  1180. *
  1181. * @since 4.3.0
  1182. *
  1183. * @return string Name of the default primary column, in this case, 'title'.
  1184. */
  1185. protected function get_default_primary_column_name() {
  1186. return 'title';
  1187. }
  1188. /**
  1189. * Generates and displays row action links.
  1190. *
  1191. * @since 4.3.0
  1192. *
  1193. * @param WP_Post $post Post being acted upon.
  1194. * @param string $column_name Current column name.
  1195. * @param string $primary Primary column name.
  1196. * @return string Row actions output for posts, or an empty string
  1197. * if the current column is not the primary column.
  1198. */
  1199. protected function handle_row_actions( $post, $column_name, $primary ) {
  1200. if ( $primary !== $column_name ) {
  1201. return '';
  1202. }
  1203. $post_type_object = get_post_type_object( $post->post_type );
  1204. $can_edit_post = current_user_can( 'edit_post', $post->ID );
  1205. $actions = array();
  1206. $title = _draft_or_post_title();
  1207. if ( $can_edit_post && 'trash' !== $post->post_status ) {
  1208. $actions['edit'] = sprintf(
  1209. '<a href="%s" aria-label="%s">%s</a>',
  1210. get_edit_post_link( $post->ID ),
  1211. /* translators: %s: Post title. */
  1212. esc_attr( sprintf( __( 'Edit &#8220;%s&#8221;' ), $title ) ),
  1213. __( 'Edit' )
  1214. );
  1215. if ( 'wp_block' !== $post->post_type ) {
  1216. $actions['inline hide-if-no-js'] = sprintf(
  1217. '<button type="button" class="button-link editinline" aria-label="%s" aria-expanded="false">%s</button>',
  1218. /* translators: %s: Post title. */
  1219. esc_attr( sprintf( __( 'Quick edit &#8220;%s&#8221; inline' ), $title ) ),
  1220. __( 'Quick&nbsp;Edit' )
  1221. );
  1222. }
  1223. }
  1224. if ( current_user_can( 'delete_post', $post->ID ) ) {
  1225. if ( 'trash' === $post->post_status ) {
  1226. $actions['untrash'] = sprintf(
  1227. '<a href="%s" aria-label="%s">%s</a>',
  1228. wp_nonce_url( admin_url( sprintf( $post_type_object->_edit_link . '&amp;action=untrash', $post->ID ) ), 'untrash-post_' . $post->ID ),
  1229. /* translators: %s: Post title. */
  1230. esc_attr( sprintf( __( 'Restore &#8220;%s&#8221; from the Trash' ), $title ) ),
  1231. __( 'Restore' )
  1232. );
  1233. } elseif ( EMPTY_TRASH_DAYS ) {
  1234. $actions['trash'] = sprintf(
  1235. '<a href="%s" class="submitdelete" aria-label="%s">%s</a>',
  1236. get_delete_post_link( $post->ID ),
  1237. /* translators: %s: Post title. */
  1238. esc_attr( sprintf( __( 'Move &#8220;%s&#8221; to the Trash' ), $title ) ),
  1239. _x( 'Trash', 'verb' )
  1240. );
  1241. }
  1242. if ( 'trash' === $post->post_status || ! EMPTY_TRASH_DAYS ) {
  1243. $actions['delete'] = sprintf(
  1244. '<a href="%s" class="submitdelete" aria-label="%s">%s</a>',
  1245. get_delete_post_link( $post->ID, '', true ),
  1246. /* translators: %s: Post title. */
  1247. esc_attr( sprintf( __( 'Delete &#8220;%s&#8221; permanently' ), $title ) ),
  1248. __( 'Delete Permanently' )
  1249. );
  1250. }
  1251. }
  1252. if ( is_post_type_viewable( $post_type_object ) ) {
  1253. if ( in_array( $post->post_status, array( 'pending', 'draft', 'future' ), true ) ) {
  1254. if ( $can_edit_post ) {
  1255. $preview_link = get_preview_post_link( $post );
  1256. $actions['view'] = sprintf(
  1257. '<a href="%s" rel="bookmark" aria-label="%s">%s</a>',
  1258. esc_url( $preview_link ),
  1259. /* translators: %s: Post title. */
  1260. esc_attr( sprintf( __( 'Preview &#8220;%s&#8221;' ), $title ) ),
  1261. __( 'Preview' )
  1262. );
  1263. }
  1264. } elseif ( 'trash' !== $post->post_status ) {
  1265. $actions['view'] = sprintf(
  1266. '<a href="%s" rel="bookmark" aria-label="%s">%s</a>',
  1267. get_permalink( $post->ID ),
  1268. /* translators: %s: Post title. */
  1269. esc_attr( sprintf( __( 'View &#8220;%s&#8221;' ), $title ) ),
  1270. __( 'View' )
  1271. );
  1272. }
  1273. }
  1274. if ( 'wp_block' === $post->post_type ) {
  1275. $actions['export'] = sprintf(
  1276. '<button type="button" class="wp-list-reusable-blocks__export button-link" data-id="%s" aria-label="%s">%s</button>',
  1277. $post->ID,
  1278. /* translators: %s: Post title. */
  1279. esc_attr( sprintf( __( 'Export &#8220;%s&#8221; as JSON' ), $title ) ),
  1280. __( 'Export as JSON' )
  1281. );
  1282. }
  1283. if ( is_post_type_hierarchical( $post->post_type ) ) {
  1284. /**
  1285. * Filters the array of row action links on the Pages list table.
  1286. *
  1287. * The filter is evaluated only for hierarchical post types.
  1288. *
  1289. * @since 2.8.0
  1290. *
  1291. * @param string[] $actions An array of row action links. Defaults are
  1292. * 'Edit', 'Quick Edit', 'Restore', 'Trash',
  1293. * 'Delete Permanently', 'Preview', and 'View'.
  1294. * @param WP_Post $post The post object.
  1295. */
  1296. $actions = apply_filters( 'page_row_actions', $actions, $post );
  1297. } else {
  1298. /**
  1299. * Filters the array of row action links on the Posts list table.
  1300. *
  1301. * The filter is evaluated only for non-hierarchical post types.
  1302. *
  1303. * @since 2.8.0
  1304. *
  1305. * @param string[] $actions An array of row action links. Defaults are
  1306. * 'Edit', 'Quick Edit', 'Restore', 'Trash',
  1307. * 'Delete Permanently', 'Preview', and 'View'.
  1308. * @param WP_Post $post The post object.
  1309. */
  1310. $actions = apply_filters( 'post_row_actions', $actions, $post );
  1311. }
  1312. return $this->row_actions( $actions );
  1313. }
  1314. /**
  1315. * Outputs the hidden row displayed when inline editing
  1316. *
  1317. * @since 3.1.0
  1318. *
  1319. * @global string $mode List table view mode.
  1320. */
  1321. public function inline_edit() {
  1322. global $mode;
  1323. $screen = $this->screen;
  1324. $post = get_default_post_to_edit( $screen->post_type );
  1325. $post_type_object = get_post_type_object( $screen->post_type );
  1326. $taxonomy_names = get_object_taxonomies( $screen->post_type );
  1327. $hierarchical_taxonomies = array();
  1328. $flat_taxonomies = array();
  1329. foreach ( $taxonomy_names as $taxonomy_name ) {
  1330. $taxonomy = get_taxonomy( $taxonomy_name );
  1331. $show_in_quick_edit = $taxonomy->show_in_quick_edit;
  1332. /**
  1333. * Filters whether the current taxonomy should be shown in the Quick Edit panel.
  1334. *
  1335. * @since 4.2.0
  1336. *
  1337. * @param bool $show_in_quick_edit Whether to show the current taxonomy in Quick Edit.
  1338. * @param string $taxonomy_name Taxonomy name.
  1339. * @param string $post_type Post type of current Quick Edit post.
  1340. */
  1341. if ( ! apply_filters( 'quick_edit_show_taxonomy', $show_in_quick_edit, $taxonomy_name, $screen->post_type ) ) {
  1342. continue;
  1343. }
  1344. if ( $taxonomy->hierarchical ) {
  1345. $hierarchical_taxonomies[] = $taxonomy;
  1346. } else {
  1347. $flat_taxonomies[] = $taxonomy;
  1348. }
  1349. }
  1350. $m = ( isset( $mode ) && 'excerpt' === $mode ) ? 'excerpt' : 'list';
  1351. $can_publish = current_user_can( $post_type_object->cap->publish_posts );
  1352. $core_columns = array(
  1353. 'cb' => true,
  1354. 'date' => true,
  1355. 'title' => true,
  1356. 'categories' => true,
  1357. 'tags' => true,
  1358. 'comments' => true,
  1359. 'author' => true,
  1360. );
  1361. ?>
  1362. <form method="get">
  1363. <table style="display: none"><tbody id="inlineedit">
  1364. <?php
  1365. $hclass = count( $hierarchical_taxonomies ) ? 'post' : 'page';
  1366. $inline_edit_classes = "inline-edit-row inline-edit-row-$hclass";
  1367. $bulk_edit_classes = "bulk-edit-row bulk-edit-row-$hclass bulk-edit-{$screen->post_type}";
  1368. $quick_edit_classes = "quick-edit-row quick-edit-row-$hclass inline-edit-{$screen->post_type}";
  1369. $bulk = 0;
  1370. while ( $bulk < 2 ) :
  1371. $classes = $inline_edit_classes . ' ';
  1372. $classes .= $bulk ? $bulk_edit_classes : $quick_edit_classes;
  1373. ?>
  1374. <tr id="<?php echo $bulk ? 'bulk-edit' : 'inline-edit'; ?>" class="<?php echo $classes; ?>" style="display: none">
  1375. <td colspan="<?php echo $this->get_column_count(); ?>" class="colspanchange">
  1376. <fieldset class="inline-edit-col-left">
  1377. <legend class="inline-edit-legend"><?php echo $bulk ? __( 'Bulk Edit' ) : __( 'Quick Edit' ); ?></legend>
  1378. <div class="inline-edit-col">
  1379. <?php if ( post_type_supports( $screen->post_type, 'title' ) ) : ?>
  1380. <?php if ( $bulk ) : ?>
  1381. <div id="bulk-title-div">
  1382. <div id="bulk-titles"></div>
  1383. </div>
  1384. <?php else : // $bulk ?>
  1385. <label>
  1386. <span class="title"><?php _e( 'Title' ); ?></span>
  1387. <span class="input-text-wrap"><input type="text" name="post_title" class="ptitle" value="" /></span>
  1388. </label>
  1389. <?php if ( is_post_type_viewable( $screen->post_type ) ) : ?>
  1390. <label>
  1391. <span class="title"><?php _e( 'Slug' ); ?></span>
  1392. <span class="input-text-wrap"><input type="text" name="post_name" value="" /></span>
  1393. </label>
  1394. <?php endif; // is_post_type_viewable() ?>
  1395. <?php endif; // $bulk ?>
  1396. <?php endif; // post_type_supports( ... 'title' ) ?>
  1397. <?php if ( ! $bulk ) : ?>
  1398. <fieldset class="inline-edit-date">
  1399. <legend><span class="title"><?php _e( 'Date' ); ?></span></legend>
  1400. <?php touch_time( 1, 1, 0, 1 ); ?>
  1401. </fieldset>
  1402. <br class="clear" />
  1403. <?php endif; // $bulk ?>
  1404. <?php
  1405. if ( post_type_supports( $screen->post_type, 'author' ) ) {
  1406. $authors_dropdown = '';
  1407. if ( current_user_can( $post_type_object->cap->edit_others_posts ) ) {
  1408. $users_opt = array(
  1409. 'hide_if_only_one_author' => false,
  1410. 'who' => 'authors',
  1411. 'name' => 'post_author',
  1412. 'class' => 'authors',
  1413. 'multi' => 1,
  1414. 'echo' => 0,
  1415. 'show' => 'display_name_with_login',
  1416. );
  1417. if ( $bulk ) {
  1418. $users_opt['show_option_none'] = __( '&mdash; No Change &mdash;' );
  1419. }
  1420. /**
  1421. * Filters the arguments used to generate the Quick Edit authors drop-down.
  1422. *
  1423. * @since 5.6.0
  1424. *
  1425. * @see wp_dropdown_users()
  1426. *
  1427. * @param array $users_opt An array of arguments passed to wp_dropdown_users().
  1428. * @param bool $bulk A flag to denote if it's a bulk action.
  1429. */
  1430. $users_opt = apply_filters( 'quick_edit_dropdown_authors_args', $users_opt, $bulk );
  1431. $authors = wp_dropdown_users( $users_opt );
  1432. if ( $authors ) {
  1433. $authors_dropdown = '<label class="inline-edit-author">';
  1434. $authors_dropdown .= '<span class="title">' . __( 'Author' ) . '</span>';
  1435. $authors_dropdown .= $authors;
  1436. $authors_dropdown .= '</label>';
  1437. }
  1438. } // current_user_can( 'edit_others_posts' )
  1439. if ( ! $bulk ) {
  1440. echo $authors_dropdown;
  1441. }
  1442. } // post_type_supports( ... 'author' )
  1443. ?>
  1444. <?php if ( ! $bulk && $can_publish ) : ?>
  1445. <div class="inline-edit-group wp-clearfix">
  1446. <label class="alignleft">
  1447. <span class="title"><?php _e( 'Password' ); ?></span>
  1448. <span class="input-text-wrap"><input type="text" name="post_password" class="inline-edit-password-input" value="" /></span>
  1449. </label>
  1450. <span class="alignleft inline-edit-or">
  1451. <?php
  1452. /* translators: Between password field and private checkbox on post quick edit interface. */
  1453. _e( '&ndash;OR&ndash;' );
  1454. ?>
  1455. </span>
  1456. <label class="alignleft inline-edit-private">
  1457. <input type="checkbox" name="keep_private" value="private" />
  1458. <span class="checkbox-title"><?php _e( 'Private' ); ?></span>
  1459. </label>
  1460. </div>
  1461. <?php endif; ?>
  1462. </div>
  1463. </fieldset>
  1464. <?php if ( count( $hierarchical_taxonomies ) && ! $bulk ) : ?>
  1465. <fieldset class="inline-edit-col-center inline-edit-categories">
  1466. <div class="inline-edit-col">
  1467. <?php foreach ( $hierarchical_taxonomies as $taxonomy ) : ?>
  1468. <span class="title inline-edit-categories-label"><?php echo esc_html( $taxonomy->labels->name ); ?></span>
  1469. <input type="hidden" name="<?php echo ( 'category' === $taxonomy->name ) ? 'post_category[]' : 'tax_input[' . esc_attr( $taxonomy->name ) . '][]'; ?>" value="0" />
  1470. <ul class="cat-checklist <?php echo esc_attr( $taxonomy->name ); ?>-checklist">
  1471. <?php wp_terms_checklist( null, array( 'taxonomy' => $taxonomy->name ) ); ?>
  1472. </ul>
  1473. <?php endforeach; // $hierarchical_taxonomies as $taxonomy ?>
  1474. </div>
  1475. </fieldset>
  1476. <?php endif; // count( $hierarchical_taxonomies ) && ! $bulk ?>
  1477. <fieldset class="inline-edit-col-right">
  1478. <div class="inline-edit-col">
  1479. <?php
  1480. if ( post_type_supports( $screen->post_type, 'author' ) && $bulk ) {
  1481. echo $authors_dropdown;
  1482. }
  1483. ?>
  1484. <?php if ( post_type_supports( $screen->post_type, 'page-attributes' ) ) : ?>
  1485. <?php if ( $post_type_object->hierarchical ) : ?>
  1486. <label>
  1487. <span class="title"><?php _e( 'Parent' ); ?></span>
  1488. <?php
  1489. $dropdown_args = array(
  1490. 'post_type' => $post_type_object->name,
  1491. 'selected' => $post->post_parent,
  1492. 'name' => 'post_parent',
  1493. 'show_option_none' => __( 'Main Page (no parent)' ),
  1494. 'option_none_value' => 0,
  1495. 'sort_column' => 'menu_order, post_title',
  1496. );
  1497. if ( $bulk ) {
  1498. $dropdown_args['show_option_no_change'] = __( '&mdash; No Change &mdash;' );
  1499. }
  1500. /**
  1501. * Filters the arguments used to generate the Quick Edit page-parent drop-down.
  1502. *
  1503. * @since 2.7.0
  1504. * @since 5.6.0 The `$bulk` parameter was added.
  1505. *
  1506. * @see wp_dropdown_pages()
  1507. *
  1508. * @param array $dropdown_args An array of arguments passed to wp_dropdown_pages().
  1509. * @param bool $bulk A flag to denote if it's a bulk action.
  1510. */
  1511. $dropdown_args = apply_filters( 'quick_edit_dropdown_pages_args', $dropdown_args, $bulk );
  1512. wp_dropdown_pages( $dropdown_args );
  1513. ?>
  1514. </label>
  1515. <?php endif; // hierarchical ?>
  1516. <?php if ( ! $bulk ) : ?>
  1517. <label>
  1518. <span class="title"><?php _e( 'Order' ); ?></span>
  1519. <span class="input-text-wrap"><input type="text" name="menu_order" class="inline-edit-menu-order-input" value="<?php echo $post->menu_order; ?>" /></span>
  1520. </label>
  1521. <?php endif; // ! $bulk ?>
  1522. <?php endif; // post_type_supports( ... 'page-attributes' ) ?>
  1523. <?php if ( 0 < count( get_page_templates( null, $screen->post_type ) ) ) : ?>
  1524. <label>
  1525. <span class="title"><?php _e( 'Template' ); ?></span>
  1526. <select name="page_template">
  1527. <?php if ( $bulk ) : ?>
  1528. <option value="-1"><?php _e( '&mdash; No Change &mdash;' ); ?></option>
  1529. <?php endif; // $bulk ?>
  1530. <?php
  1531. /** This filter is documented in wp-admin/includes/meta-boxes.php */
  1532. $default_title = apply_filters( 'default_page_template_title', __( 'Default template' ), 'quick-edit' );
  1533. ?>
  1534. <option value="default"><?php echo esc_html( $default_title ); ?></option>
  1535. <?php page_template_dropdown( '', $screen->post_type ); ?>
  1536. </select>
  1537. </label>
  1538. <?php endif; ?>
  1539. <?php if ( count( $flat_taxonomies ) && ! $bulk ) : ?>
  1540. <?php foreach ( $flat_taxonomies as $taxonomy ) : ?>
  1541. <?php if ( current_user_can( $taxonomy->cap->assign_terms ) ) : ?>
  1542. <?php $taxonomy_name = esc_attr( $taxonomy->name ); ?>
  1543. <label class="inline-edit-tags">
  1544. <span class="title"><?php echo esc_html( $taxonomy->labels->name ); ?></span>
  1545. <textarea data-wp-taxonomy="<?php echo $taxonomy_name; ?>" cols="22" rows="1" name="tax_input[<?php echo $taxonomy_name; ?>]" class="tax_input_<?php echo $taxonomy_name; ?>"></textarea>
  1546. </label>
  1547. <?php endif; // current_user_can( 'assign_terms' ) ?>
  1548. <?php endforeach; // $flat_taxonomies as $taxonomy ?>
  1549. <?php endif; // count( $flat_taxonomies ) && ! $bulk ?>
  1550. <?php if ( post_type_supports( $screen->post_type, 'comments' ) || post_type_supports( $screen->post_type, 'trackbacks' ) ) : ?>
  1551. <?php if ( $bulk ) : ?>
  1552. <div class="inline-edit-group wp-clearfix">
  1553. <?php if ( post_type_supports( $screen->post_type, 'comments' ) ) : ?>
  1554. <label class="alignleft">
  1555. <span class="title"><?php _e( 'Comments' ); ?></span>
  1556. <select name="comment_status">
  1557. <option value=""><?php _e( '&mdash; No Change &mdash;' ); ?></option>
  1558. <option value="open"><?php _e( 'Allow' ); ?></option>
  1559. <option value="closed"><?php _e( 'Do not allow' ); ?></option>
  1560. </select>
  1561. </label>
  1562. <?php endif; ?>
  1563. <?php if ( post_type_supports( $screen->post_type, 'trackbacks' ) ) : ?>
  1564. <label class="alignright">
  1565. <span class="title"><?php _e( 'Pings' ); ?></span>
  1566. <select name="ping_status">
  1567. <option value=""><?php _e( '&mdash; No Change &mdash;' ); ?></option>
  1568. <option value="open"><?php _e( 'Allow' ); ?></option>
  1569. <option value="closed"><?php _e( 'Do not allow' ); ?></option>
  1570. </select>
  1571. </label>
  1572. <?php endif; ?>
  1573. </div>
  1574. <?php else : // $bulk ?>
  1575. <div class="inline-edit-group wp-clearfix">
  1576. <?php if ( post_type_supports( $screen->post_type, 'comments' ) ) : ?>
  1577. <label class="alignleft">
  1578. <input type="checkbox" name="comment_status" value="open" />
  1579. <span class="checkbox-title"><?php _e( 'Allow Comments' ); ?></span>
  1580. </label>
  1581. <?php endif; ?>
  1582. <?php if ( post_type_supports( $screen->post_type, 'trackbacks' ) ) : ?>
  1583. <label class="alignleft">
  1584. <input type="checkbox" name="ping_status" value="open" />
  1585. <span class="checkbox-title"><?php _e( 'Allow Pings' ); ?></span>
  1586. </label>
  1587. <?php endif; ?>
  1588. </div>
  1589. <?php endif; // $bulk ?>
  1590. <?php endif; // post_type_supports( ... comments or pings ) ?>
  1591. <div class="inline-edit-group wp-clearfix">
  1592. <label class="inline-edit-status alignleft">
  1593. <span class="title"><?php _e( 'Status' ); ?></span>
  1594. <select name="_status">
  1595. <?php if ( $bulk ) : ?>
  1596. <option value="-1"><?php _e( '&mdash; No Change &mdash;' ); ?></option>
  1597. <?php endif; // $bulk ?>
  1598. <?php if ( $can_publish ) : // Contributors only get "Unpublished" and "Pending Review". ?>
  1599. <option value="publish"><?php _e( 'Published' ); ?></option>
  1600. <option value="future"><?php _e( 'Scheduled' ); ?></option>
  1601. <?php if ( $bulk ) : ?>
  1602. <option value="private"><?php _e( 'Private' ); ?></option>
  1603. <?php endif; // $bulk ?>
  1604. <?php endif; ?>
  1605. <option value="pending"><?php _e( 'Pending Review' ); ?></option>
  1606. <option value="draft"><?php _e( 'Draft' ); ?></option>
  1607. </select>
  1608. </label>
  1609. <?php if ( 'post' === $screen->post_type && $can_publish && current_user_can( $post_type_object->cap->edit_others_posts ) ) : ?>
  1610. <?php if ( $bulk ) : ?>
  1611. <label class="alignright">
  1612. <span class="title"><?php _e( 'Sticky' ); ?></span>
  1613. <select name="sticky">
  1614. <option value="-1"><?php _e( '&mdash; No Change &mdash;' ); ?></option>
  1615. <option value="sticky"><?php _e( 'Sticky' ); ?></option>
  1616. <option value="unsticky"><?php _e( 'Not Sticky' ); ?></option>
  1617. </select>
  1618. </label>
  1619. <?php else : // $bulk ?>
  1620. <label class="alignleft">
  1621. <input type="checkbox" name="sticky" value="sticky" />
  1622. <span class="checkbox-title"><?php _e( 'Make this post sticky' ); ?></span>
  1623. </label>
  1624. <?php endif; // $bulk ?>
  1625. <?php endif; // 'post' && $can_publish && current_user_can( 'edit_others_posts' ) ?>
  1626. </div>
  1627. <?php if ( $bulk && current_theme_supports( 'post-formats' ) && post_type_supports( $screen->post_type, 'post-formats' ) ) : ?>
  1628. <?php $post_formats = get_theme_support( 'post-formats' ); ?>
  1629. <label class="alignleft">
  1630. <span class="title"><?php _ex( 'Format', 'post format' ); ?></span>
  1631. <select name="post_format">
  1632. <option value="-1"><?php _e( '&mdash; No Change &mdash;' ); ?></option>
  1633. <option value="0"><?php echo get_post_format_string( 'standard' ); ?></option>
  1634. <?php if ( is_array( $post_formats[0] ) ) : ?>
  1635. <?php foreach ( $post_formats[0] as $format ) : ?>
  1636. <option value="<?php echo esc_attr( $format ); ?>"><?php echo esc_html( get_post_format_string( $format ) ); ?></option>
  1637. <?php endforeach; ?>
  1638. <?php endif; ?>
  1639. </select>
  1640. </label>
  1641. <?php endif; ?>
  1642. </div>
  1643. </fieldset>
  1644. <?php
  1645. list( $columns ) = $this->get_column_info();
  1646. foreach ( $columns as $column_name => $column_display_name ) {
  1647. if ( isset( $core_columns[ $column_name ] ) ) {
  1648. continue;
  1649. }
  1650. if ( $bulk ) {
  1651. /**
  1652. * Fires once for each column in Bulk Edit mode.
  1653. *
  1654. * @since 2.7.0
  1655. *
  1656. * @param string $column_name Name of the column to edit.
  1657. * @param string $post_type The post type slug.
  1658. */
  1659. do_action( 'bulk_edit_custom_box', $column_name, $screen->post_type );
  1660. } else {
  1661. /**
  1662. * Fires once for each column in Quick Edit mode.
  1663. *
  1664. * @since 2.7.0
  1665. *
  1666. * @param string $column_name Name of the column to edit.
  1667. * @param string $post_type The post type slug, or current screen name if this is a taxonomy list table.
  1668. * @param string $taxonomy The taxonomy name, if any.
  1669. */
  1670. do_action( 'quick_edit_custom_box', $column_name, $screen->post_type, '' );
  1671. }
  1672. }
  1673. ?>
  1674. <div class="submit inline-edit-save">
  1675. <button type="button" class="button cancel alignleft"><?php _e( 'Cancel' ); ?></button>
  1676. <?php if ( ! $bulk ) : ?>
  1677. <?php wp_nonce_field( 'inlineeditnonce', '_inline_edit', false ); ?>
  1678. <button type="button" class="button button-primary save alignright"><?php _e( 'Update' ); ?></button>
  1679. <span class="spinner"></span>
  1680. <?php else : ?>
  1681. <?php submit_button( __( 'Update' ), 'primary alignright', 'bulk_edit', false ); ?>
  1682. <?php endif; ?>
  1683. <input type="hidden" name="post_view" value="<?php echo esc_attr( $m ); ?>" />
  1684. <input type="hidden" name="screen" value="<?php echo esc_attr( $screen->id ); ?>" />
  1685. <?php if ( ! $bulk && ! post_type_supports( $screen->post_type, 'author' ) ) : ?>
  1686. <input type="hidden" name="post_author" value="<?php echo esc_attr( $post->post_author ); ?>" />
  1687. <?php endif; ?>
  1688. <br class="clear" />
  1689. <div class="notice notice-error notice-alt inline hidden">
  1690. <p class="error"></p>
  1691. </div>
  1692. </div>
  1693. </td></tr>
  1694. <?php
  1695. $bulk++;
  1696. endwhile;
  1697. ?>
  1698. </tbody></table>
  1699. </form>
  1700. <?php
  1701. }
  1702. }